Feat: phishing detection
Signed-off-by: Hamza Mahjoubi <hamzamahjoubi221@gmail.com>
This commit is contained in:
Родитель
e63f1e0722
Коммит
9b87b23bc2
|
@ -51,9 +51,27 @@ class Address implements JsonSerializable {
|
|||
// Fallback
|
||||
return $this->getEmail();
|
||||
}
|
||||
$personal = trim(explode('<', $personal)[0]); // Remove the email part if present
|
||||
return $personal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCustomEmail(): ?string {
|
||||
$personal = $this->wrapped->personal;
|
||||
if ($personal === null) {
|
||||
// Fallback
|
||||
return null;
|
||||
}
|
||||
$parts = explode('<', $personal);
|
||||
if (count($parts) === 1) {
|
||||
return null;
|
||||
}
|
||||
$customEmail = trim($parts[1], '>');
|
||||
return $customEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
|
|
|
@ -25,6 +25,7 @@ use OCA\Mail\Exception\ServiceException;
|
|||
use OCA\Mail\IMAP\Charset\Converter;
|
||||
use OCA\Mail\Model\IMAPMessage;
|
||||
use OCA\Mail\Service\Html;
|
||||
use OCA\Mail\Service\PhishingDetection\PhishingDetectionService;
|
||||
use OCA\Mail\Service\SmimeService;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use function str_starts_with;
|
||||
|
@ -36,8 +37,10 @@ class ImapMessageFetcher {
|
|||
|
||||
private Html $htmlService;
|
||||
private SmimeService $smimeService;
|
||||
private PhishingDetectionService $phishingDetectionService;
|
||||
private string $userId;
|
||||
|
||||
private bool $runPhishingCheck = false;
|
||||
// Conditional fetching/parsing
|
||||
private bool $loadBody = false;
|
||||
|
||||
|
@ -54,6 +57,7 @@ class ImapMessageFetcher {
|
|||
private string $rawReferences = '';
|
||||
private string $dispositionNotificationTo = '';
|
||||
private bool $hasDkimSignature = false;
|
||||
private array $phishingDetails = [];
|
||||
private ?string $unsubscribeUrl = null;
|
||||
private bool $isOneClickUnsubscribe = false;
|
||||
private ?string $unsubscribeMailto = null;
|
||||
|
@ -64,13 +68,16 @@ class ImapMessageFetcher {
|
|||
string $userId,
|
||||
Html $htmlService,
|
||||
SmimeService $smimeService,
|
||||
private Converter $converter) {
|
||||
private Converter $converter,
|
||||
PhishingDetectionService $phishingDetectionService,
|
||||
) {
|
||||
$this->uid = $uid;
|
||||
$this->mailbox = $mailbox;
|
||||
$this->client = $client;
|
||||
$this->userId = $userId;
|
||||
$this->htmlService = $htmlService;
|
||||
$this->smimeService = $smimeService;
|
||||
$this->phishingDetectionService = $phishingDetectionService;
|
||||
}
|
||||
|
||||
|
||||
|
@ -85,6 +92,17 @@ class ImapMessageFetcher {
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the fetcher to check for phishing.
|
||||
*
|
||||
* @param bool $value
|
||||
* @return $this
|
||||
*/
|
||||
public function withPhishingCheck(bool $value): ImapMessageFetcher {
|
||||
$this->runPhishingCheck = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Horde_Imap_Client_Data_Fetch|null $fetch
|
||||
* Will be reused if no body is requested.
|
||||
|
@ -238,6 +256,7 @@ class ImapMessageFetcher {
|
|||
$this->rawReferences,
|
||||
$this->dispositionNotificationTo,
|
||||
$this->hasDkimSignature,
|
||||
$this->phishingDetails,
|
||||
$this->unsubscribeUrl,
|
||||
$this->isOneClickUnsubscribe,
|
||||
$this->unsubscribeMailto,
|
||||
|
@ -495,6 +514,10 @@ class ImapMessageFetcher {
|
|||
$dkimSignatureHeader = $parsedHeaders->getHeader('dkim-signature');
|
||||
$this->hasDkimSignature = $dkimSignatureHeader !== null;
|
||||
|
||||
if ($this->runPhishingCheck) {
|
||||
$this->phishingDetails = $this->phishingDetectionService->checkHeadersForPhishing($parsedHeaders, $this->hasHtmlMessage, $this->htmlMessage);
|
||||
}
|
||||
|
||||
$listUnsubscribeHeader = $parsedHeaders->getHeader('list-unsubscribe');
|
||||
if ($listUnsubscribeHeader !== null) {
|
||||
$listHeaders = new Horde_ListHeaders();
|
||||
|
|
|
@ -12,19 +12,23 @@ namespace OCA\Mail\IMAP;
|
|||
use Horde_Imap_Client_Base;
|
||||
use OCA\Mail\IMAP\Charset\Converter;
|
||||
use OCA\Mail\Service\Html;
|
||||
use OCA\Mail\Service\PhishingDetection\PhishingDetectionService;
|
||||
use OCA\Mail\Service\SmimeService;
|
||||
|
||||
class ImapMessageFetcherFactory {
|
||||
private Html $htmlService;
|
||||
private SmimeService $smimeService;
|
||||
private Converter $charsetConverter;
|
||||
private PhishingDetectionService $phishingDetectionService;
|
||||
|
||||
public function __construct(Html $htmlService,
|
||||
SmimeService $smimeService,
|
||||
Converter $charsetConverter) {
|
||||
Converter $charsetConverter,
|
||||
PhishingDetectionService $phishingDetectionService) {
|
||||
$this->htmlService = $htmlService;
|
||||
$this->smimeService = $smimeService;
|
||||
$this->charsetConverter = $charsetConverter;
|
||||
$this->phishingDetectionService = $phishingDetectionService;
|
||||
}
|
||||
|
||||
public function build(int $uid,
|
||||
|
@ -39,6 +43,7 @@ class ImapMessageFetcherFactory {
|
|||
$this->htmlService,
|
||||
$this->smimeService,
|
||||
$this->charsetConverter,
|
||||
$this->phishingDetectionService,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ class MessageMapper {
|
|||
int $id,
|
||||
string $userId,
|
||||
bool $loadBody = false): IMAPMessage {
|
||||
$result = $this->findByIds($client, $mailbox, new Horde_Imap_Client_Ids([$id]), $userId, $loadBody);
|
||||
$result = $this->findByIds($client, $mailbox, new Horde_Imap_Client_Ids([$id]), $userId, $loadBody, true);
|
||||
|
||||
if (count($result) === 0) {
|
||||
throw new DoesNotExistException("Message does not exist");
|
||||
|
@ -249,7 +249,8 @@ class MessageMapper {
|
|||
string $mailbox,
|
||||
$ids,
|
||||
string $userId,
|
||||
bool $loadBody = false): array {
|
||||
bool $loadBody = false,
|
||||
bool $runPhishingCheck = false): array {
|
||||
$query = new Horde_Imap_Client_Fetch_Query();
|
||||
$query->envelope();
|
||||
$query->flags();
|
||||
|
@ -294,7 +295,7 @@ class MessageMapper {
|
|||
$this->logger->debug("findByIds in $mailbox got " . count($ids) . " UIDs ($range) and found " . count($fetchResults) . ". minFetched=$minFetched maxFetched=$maxFetched");
|
||||
}
|
||||
|
||||
return array_map(function (Horde_Imap_Client_Data_Fetch $fetchResult) use ($client, $mailbox, $loadBody, $userId) {
|
||||
return array_map(function (Horde_Imap_Client_Data_Fetch $fetchResult) use ($client, $mailbox, $loadBody, $userId, $runPhishingCheck) {
|
||||
return $this->imapMessageFactory
|
||||
->build(
|
||||
$fetchResult->getUid(),
|
||||
|
@ -303,6 +304,7 @@ class MessageMapper {
|
|||
$userId,
|
||||
)
|
||||
->withBody($loadBody)
|
||||
->withPhishingCheck($runPhishingCheck)
|
||||
->fetchMessage($fetchResult);
|
||||
}, $fetchResults);
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ class IMAPMessage implements IMessage, JsonSerializable {
|
|||
private string $rawReferences;
|
||||
private string $dispositionNotificationTo;
|
||||
private bool $hasDkimSignature;
|
||||
private array $phishingDetails;
|
||||
private ?string $unsubscribeUrl;
|
||||
private bool $isOneClickUnsubscribe;
|
||||
private ?string $unsubscribeMailto;
|
||||
|
@ -85,6 +86,7 @@ class IMAPMessage implements IMessage, JsonSerializable {
|
|||
string $rawReferences,
|
||||
string $dispositionNotificationTo,
|
||||
bool $hasDkimSignature,
|
||||
array $phishingDetails,
|
||||
?string $unsubscribeUrl,
|
||||
bool $isOneClickUnsubscribe,
|
||||
?string $unsubscribeMailto,
|
||||
|
@ -113,6 +115,7 @@ class IMAPMessage implements IMessage, JsonSerializable {
|
|||
$this->rawReferences = $rawReferences;
|
||||
$this->dispositionNotificationTo = $dispositionNotificationTo;
|
||||
$this->hasDkimSignature = $hasDkimSignature;
|
||||
$this->phishingDetails = $phishingDetails;
|
||||
$this->unsubscribeUrl = $unsubscribeUrl;
|
||||
$this->isOneClickUnsubscribe = $isOneClickUnsubscribe;
|
||||
$this->unsubscribeMailto = $unsubscribeMailto;
|
||||
|
@ -299,6 +302,7 @@ class IMAPMessage implements IMessage, JsonSerializable {
|
|||
'hasHtmlBody' => $this->hasHtmlMessage,
|
||||
'dispositionNotificationTo' => $this->getDispositionNotificationTo(),
|
||||
'hasDkimSignature' => $this->hasDkimSignature,
|
||||
'phishingDetails' => $this->phishingDetails,
|
||||
'unsubscribeUrl' => $this->unsubscribeUrl,
|
||||
'isOneClickUnsubscribe' => $this->isOneClickUnsubscribe,
|
||||
'unsubscribeMailto' => $this->unsubscribeMailto,
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Mail;
|
||||
|
||||
use JsonSerializable;
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
class PhishingDetectionList implements JsonSerializable {
|
||||
|
||||
/** @var PhishingDetectionResult[] */
|
||||
private array $checks;
|
||||
|
||||
private bool $warning = false;
|
||||
|
||||
/**
|
||||
* @param PhishingDetectionResult[] $checks
|
||||
*/
|
||||
public function __construct(array $checks = []) {
|
||||
$this->checks = $checks;
|
||||
}
|
||||
|
||||
public function addCheck(PhishingDetectionResult $check) {
|
||||
$this->checks[] = $check;
|
||||
}
|
||||
|
||||
private function isWarning() {
|
||||
foreach ($this->checks as $check) {
|
||||
if (in_array($check->getType(), [PhishingDetectionResult::DATE_CHECK, PhishingDetectionResult::LINK_CHECK, PhishingDetectionResult::CUSTOM_EMAIL_CHECK, PhishingDetectionResult::CONTACTS_CHECK]) && $check->isPhishing()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#[ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
$result = array_map(static function (PhishingDetectionResult $check) {
|
||||
return $check->jsonSerialize();
|
||||
}, $this->checks);
|
||||
return [
|
||||
'checks' => $result,
|
||||
'warning' => $this->isWarning(),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCA\Mail;
|
||||
|
||||
use JsonSerializable;
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
/**
|
||||
* @psalm-immutable
|
||||
*/
|
||||
class PhishingDetectionResult implements JsonSerializable {
|
||||
|
||||
public const DATE_CHECK = "Date";
|
||||
public const LINK_CHECK = "Link";
|
||||
public const REPLYTO_CHECK = "Reply-To";
|
||||
public const CUSTOM_EMAIL_CHECK = "Custom Email";
|
||||
public const CONTACTS_CHECK = "Contacts";
|
||||
public const TRUSTED_CHECK = "Trusted";
|
||||
|
||||
private string $message = "";
|
||||
private bool $isPhishing;
|
||||
private array $additionalData = [];
|
||||
private string $type;
|
||||
|
||||
public function __construct(string $type, bool $isPhishing, string $message = "", array $additionalData = []) {
|
||||
$this->type = $type;
|
||||
$this->message = $message;
|
||||
$this->isPhishing = $isPhishing;
|
||||
$this->additionalData = $additionalData;
|
||||
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function isPhishing(): bool {
|
||||
return $this->isPhishing;
|
||||
}
|
||||
|
||||
#[ReturnTypeWillChange]
|
||||
public function jsonSerialize() {
|
||||
return [
|
||||
'type' => $this->type,
|
||||
'isPhishing' => $this->isPhishing,
|
||||
'message' => $this->message,
|
||||
'additionalData' => $this->additionalData,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -223,7 +223,7 @@ class ContactsIntegration {
|
|||
/**
|
||||
* @param string[] $fields
|
||||
*/
|
||||
private function doSearch(string $term, array $fields, bool $strictSearch): array {
|
||||
private function doSearch(string $term, array $fields, bool $strictSearch, bool $forceSAB = false) : array {
|
||||
$allowSystemUsers = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'no') === 'yes';
|
||||
|
||||
$result = $this->contactsManager->search($term, $fields, [
|
||||
|
@ -231,14 +231,16 @@ class ContactsIntegration {
|
|||
]);
|
||||
$matches = [];
|
||||
foreach ($result as $r) {
|
||||
if (!$allowSystemUsers && isset($r['isLocalSystemBook']) && $r['isLocalSystemBook']) {
|
||||
if ((!$allowSystemUsers && !$forceSAB) && isset($r['isLocalSystemBook']) && $r['isLocalSystemBook']) {
|
||||
continue;
|
||||
}
|
||||
$id = $r['UID'];
|
||||
$fn = $r['FN'];
|
||||
$email = $r['EMAIL'];
|
||||
$matches[] = [
|
||||
'id' => $id,
|
||||
'label' => $fn,
|
||||
'email' => $email,
|
||||
];
|
||||
}
|
||||
return $matches;
|
||||
|
@ -257,7 +259,7 @@ class ContactsIntegration {
|
|||
/**
|
||||
* Extracts all Contacts with the specified name
|
||||
*/
|
||||
public function getContactsWithName(string $name): array {
|
||||
return $this->doSearch($name, ['FN'], false);
|
||||
public function getContactsWithName(string $name, bool $forceSAB = false): array {
|
||||
return $this->doSearch($name, ['FN'], false, $forceSAB);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Mail\Service\PhishingDetection;
|
||||
|
||||
use OCA\Mail\PhishingDetectionResult;
|
||||
use OCA\Mail\Service\ContactsIntegration;
|
||||
use OCP\IL10N;
|
||||
|
||||
class ContactCheck {
|
||||
public function __construct(private ContactsIntegration $contactIntegration, private IL10N $l10n) {
|
||||
$this->l10n = $l10n;
|
||||
$this->contactIntegration = $contactIntegration;
|
||||
}
|
||||
|
||||
public function run(string $fn, string $email): PhishingDetectionResult {
|
||||
$emailInContacts = false;
|
||||
$emails = "";
|
||||
$contacts = $this->contactIntegration->getContactsWithName($fn, true);
|
||||
foreach ($contacts as $contact) {
|
||||
foreach ($contact['email'] as $contactEmail) {
|
||||
$emailInContacts = true;
|
||||
$emails .= $contactEmail.",";
|
||||
if ($contactEmail === $email) {
|
||||
return new PhishingDetectionResult(PhishingDetectionResult::CONTACTS_CHECK, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($emailInContacts) {
|
||||
return new PhishingDetectionResult(PhishingDetectionResult::CONTACTS_CHECK, true, $this->l10n->t('Sender email: %1$s is not in the address book, but the sender name: %2$s is in the address book with the following emails: %3$s', [$email, $fn, $emails]));
|
||||
}
|
||||
|
||||
return new PhishingDetectionResult(PhishingDetectionResult::CONTACTS_CHECK, false);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Mail\Service\PhishingDetection;
|
||||
|
||||
use OCA\Mail\PhishingDetectionResult;
|
||||
use OCP\IL10N;
|
||||
|
||||
class CustomEmailCheck {
|
||||
protected IL10N $l10n;
|
||||
|
||||
public function __construct(IL10N $l10n) {
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
||||
public function run(string $fromEmail, ?string $customEmail): PhishingDetectionResult {
|
||||
if(!(isset($customEmail))) {
|
||||
return new PhishingDetectionResult(PhishingDetectionResult::CUSTOM_EMAIL_CHECK, false);
|
||||
}
|
||||
if($fromEmail === $customEmail) {
|
||||
return new PhishingDetectionResult(PhishingDetectionResult::CUSTOM_EMAIL_CHECK, false);
|
||||
}
|
||||
return new PhishingDetectionResult(PhishingDetectionResult::CUSTOM_EMAIL_CHECK, true, $this->l10n->t('Sender is using a custom email: %1$s instead of the sender email: %2$s', [$customEmail, $fromEmail]));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Mail\Service\PhishingDetection;
|
||||
|
||||
use OCA\Mail\PhishingDetectionResult;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IL10N;
|
||||
|
||||
class DateCheck {
|
||||
protected IL10N $l10n;
|
||||
protected ITimeFactory $timeFactory;
|
||||
|
||||
public function __construct(IL10N $l10n, ITimeFactory $timeFactory) {
|
||||
$this->l10n = $l10n;
|
||||
$this->timeFactory = $timeFactory;
|
||||
}
|
||||
|
||||
public function run(string $date): PhishingDetectionResult {
|
||||
$now = $this->timeFactory->getDateTime('now');
|
||||
$dt = $this->timeFactory->getDateTime($date);
|
||||
if ($dt > $now) {
|
||||
return new PhishingDetectionResult(PhishingDetectionResult::DATE_CHECK, true, $this->l10n->t("Sent date is in the future"));
|
||||
}
|
||||
return new PhishingDetectionResult(PhishingDetectionResult::DATE_CHECK, false);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Mail\Service\PhishingDetection;
|
||||
|
||||
use Horde_Mime_Headers;
|
||||
use OCA\Mail\AddressList;
|
||||
use OCA\Mail\PhishingDetectionList;
|
||||
|
||||
class PhishingDetectionService {
|
||||
public function __construct(private ContactCheck $contactCheck, private CustomEmailCheck $customEmailCheck, private DateCheck $dateCheck, private ReplyToCheck $replyToCheck) {
|
||||
$this->contactCheck = $contactCheck;
|
||||
$this->customEmailCheck = $customEmailCheck;
|
||||
$this->dateCheck = $dateCheck;
|
||||
$this->replyToCheck = $replyToCheck;
|
||||
}
|
||||
|
||||
|
||||
public function checkHeadersForPhishing(Horde_Mime_Headers $headers, bool $hasHtmlMessage, string $htmlMessage = ''): array {
|
||||
$list = new PhishingDetectionList();
|
||||
/** @psalm-suppress UndefinedMethod */
|
||||
$fromFN = AddressList::fromHorde($headers->getHeader('From')->getAddressList(true))->first()->getLabel();
|
||||
/** @psalm-suppress UndefinedMethod */
|
||||
$fromEmail = AddressList::fromHorde($headers->getHeader('From')->getAddressList(true))->first()->getEmail();
|
||||
/** @psalm-suppress UndefinedMethod */
|
||||
$replyToEmailHeader = $headers->getHeader('Reply-To')?->getAddressList(true);
|
||||
$replyToEmail = isset($replyToEmailHeader)? AddressList::fromHorde($replyToEmailHeader)->first()->getEmail() : null ;
|
||||
$date = $headers->getHeader('Date')->__get('value');
|
||||
/** @psalm-suppress UndefinedMethod */
|
||||
$customEmail = AddressList::fromHorde($headers->getHeader('From')->getAddressList(true))->first()->getCustomEmail();
|
||||
$list->addCheck($this->replyToCheck->run($fromEmail, $replyToEmail));
|
||||
$list->addCheck($this->contactCheck->run($fromFN, $fromEmail));
|
||||
$list->addCheck($this->dateCheck->run($date));
|
||||
$list->addCheck($this->customEmailCheck->run($fromEmail, $customEmail));
|
||||
return $list->jsonSerialize();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Mail\Service\PhishingDetection;
|
||||
|
||||
use OCA\Mail\PhishingDetectionResult;
|
||||
use OCP\IL10N;
|
||||
|
||||
class ReplyToCheck {
|
||||
protected IL10N $l10n;
|
||||
|
||||
public function __construct(IL10N $l10n) {
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
||||
public function run(string $fromEmail, ?string $replyToEmail) :PhishingDetectionResult {
|
||||
if ($replyToEmail === null) {
|
||||
return new PhishingDetectionResult(PhishingDetectionResult::REPLYTO_CHECK, false);
|
||||
}
|
||||
if ($fromEmail === $replyToEmail) {
|
||||
return new PhishingDetectionResult(PhishingDetectionResult::REPLYTO_CHECK, false);
|
||||
}
|
||||
|
||||
return new PhishingDetectionResult(PhishingDetectionResult::REPLYTO_CHECK, true, $this->l10n->t('Reply-To email: %1$s is different from the sender email: %2$s', [$replyToEmail, $fromEmail]));
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
<div :class="[message.hasHtmlBody ? 'mail-message-body mail-message-body-html' : 'mail-message-body']"
|
||||
role="region"
|
||||
:aria-label="t('mail','Message body')">
|
||||
<PhishingWarning v-if="message.phishingDetails.warning" :phishing-data="message.phishingDetails.checks" />
|
||||
<div v-if="message.smime.isSigned && !message.smime.signatureIsValid"
|
||||
class="invalid-signature-warning">
|
||||
<LockOffIcon :size="20"
|
||||
|
@ -68,6 +69,7 @@ import { html, plain } from '../util/text.js'
|
|||
import { isPgpgMessage } from '../crypto/pgp.js'
|
||||
import Itinerary from './Itinerary.vue'
|
||||
import MessageAttachments from './MessageAttachments.vue'
|
||||
import PhishingWarning from './PhishingWarning.vue'
|
||||
import MessageEncryptedBody from './MessageEncryptedBody.vue'
|
||||
import MessageHTMLBody from './MessageHTMLBody.vue'
|
||||
import MessagePlainTextBody from './MessagePlainTextBody.vue'
|
||||
|
@ -83,6 +85,7 @@ export default {
|
|||
MessageEncryptedBody,
|
||||
MessageHTMLBody,
|
||||
MessagePlainTextBody,
|
||||
PhishingWarning,
|
||||
Imip,
|
||||
LockOffIcon,
|
||||
ReplyIcon,
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
<template>
|
||||
<div class="warning">
|
||||
<div class="warning__title">
|
||||
<IconAlertOutline :size="20" :title="t('mail', 'Phishing email')" />
|
||||
This email might be a phishing attempt
|
||||
</div>
|
||||
<ul v-for="(warning,index) in warnings" :key="index" class="warning__list">
|
||||
<li>{{ warning.message }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import IconAlertOutline from 'vue-material-design-icons/AlertOutline.vue'
|
||||
|
||||
export default {
|
||||
|
||||
name: 'PhishingWarning',
|
||||
components: {
|
||||
IconAlertOutline,
|
||||
},
|
||||
props: {
|
||||
phishingData: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showMore: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
warnings() {
|
||||
return this.phishingData.filter(check => check.isPhishing)
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.warning {
|
||||
background-color:var(--ck-color-base-error);
|
||||
border-radius: var(--border-radius-rounded);
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
&__title {
|
||||
display: flex;
|
||||
}
|
||||
&__list {
|
||||
list-style-position: inside;
|
||||
list-style-type: disc;
|
||||
}
|
||||
&__links {
|
||||
margin-top: 10px;
|
||||
&__button{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
|
||||
namespace OCA\Mail\Tests\Integration\Service\Phishing;
|
||||
|
||||
use ChristophWurst\Nextcloud\Testing\TestCase;
|
||||
use Horde_Mime_Headers;
|
||||
use OCA\Mail\Service\ContactsIntegration;
|
||||
|
||||
use OCA\Mail\Service\PhishingDetection\ContactCheck;
|
||||
use OCA\Mail\Service\PhishingDetection\CustomEmailCheck;
|
||||
use OCA\Mail\Service\PhishingDetection\DateCheck;
|
||||
use OCA\Mail\Service\PhishingDetection\PhishingDetectionService;
|
||||
use OCA\Mail\Service\PhishingDetection\ReplyToCheck;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IL10N;
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
||||
class PhishingDetectionServiceIntegrationTest extends TestCase {
|
||||
|
||||
private ContactsIntegration|MockObject $contactsIntegration;
|
||||
private IL10N|MockObject $l10n;
|
||||
private ITimeFactory $timeFactory;
|
||||
private ContactCheck $contactCheck;
|
||||
private CustomEmailCheck $customEmailCheck;
|
||||
private DateCheck $dateCheck;
|
||||
private ReplyToCheck $replyToCheck;
|
||||
private PhishingDetectionService $service;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->contactsIntegration = $this->createMock(ContactsIntegration::class);
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->contactCheck = new ContactCheck($this->contactsIntegration, $this->l10n);
|
||||
$this->customEmailCheck = new CustomEmailCheck($this->l10n);
|
||||
$this->dateCheck = new DateCheck($this->l10n, \OC::$server->get(ITimeFactory::class));
|
||||
$this->replyToCheck = new ReplyToCheck($this->l10n);
|
||||
$this->service = new PhishingDetectionService($this->contactCheck, $this->customEmailCheck, $this->dateCheck, $this->replyToCheck);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function testContactCheck(): void {
|
||||
$this->contactsIntegration->expects($this->once())
|
||||
->method('getContactsWithName')
|
||||
->willReturn([["id" => 1, "fn" => "John Doe", "email" => ["jhon@example.org","Doe@example.org"]]]);
|
||||
$result = $this->contactCheck->run("John Doe", "jhon.doe@example.org");
|
||||
$this->assertTrue($result->isPhishing());
|
||||
}
|
||||
|
||||
public function testCustomEmailCheck(): void {
|
||||
$result = $this->customEmailCheck->run("jhon@example.org", "jhon.doe@example.org");
|
||||
$this->assertTrue($result->isPhishing());
|
||||
}
|
||||
|
||||
public function testReplyToCheck(): void {
|
||||
$result = $this->replyToCheck->run("jhon@example.org", "jhon.doe@example.org");
|
||||
$this->assertTrue($result->isPhishing());
|
||||
}
|
||||
public function testCheckHeadersForPhishing(): void {
|
||||
$headerStream = fopen(__DIR__ . '/../../../data/phishing-mail-headers.txt', 'r');
|
||||
$parsedHeaders = Horde_Mime_Headers::parseHeaders($headerStream);
|
||||
fclose($headerStream);
|
||||
$result = $this->service->checkHeadersForPhishing($parsedHeaders, false);
|
||||
$this->assertTrue($result["warning"]);
|
||||
}
|
||||
|
||||
}
|
|
@ -134,6 +134,7 @@ class ListControllerTest extends TestCase {
|
|||
'',
|
||||
'',
|
||||
false,
|
||||
[],
|
||||
null,
|
||||
false,
|
||||
'',
|
||||
|
@ -196,6 +197,7 @@ class ListControllerTest extends TestCase {
|
|||
'',
|
||||
'',
|
||||
false,
|
||||
[],
|
||||
'https://un.sub.scribe/me',
|
||||
true,
|
||||
'',
|
||||
|
|
|
@ -61,6 +61,7 @@ class MessageMapperTest extends TestCase {
|
|||
$ids = [1, 3];
|
||||
$userId = 'user';
|
||||
$loadBody = false;
|
||||
$runPhishingCheck = false;
|
||||
|
||||
$imapMessageFetcher1 = $this->createMock(ImapMessageFetcher::class);
|
||||
$imapMessageFetcher2 = $this->createMock(ImapMessageFetcher::class);
|
||||
|
@ -98,6 +99,10 @@ class MessageMapperTest extends TestCase {
|
|||
->method('withBody')
|
||||
->with($loadBody)
|
||||
->willReturnSelf();
|
||||
$imapMessageFetcher1->expects(self::once())
|
||||
->method('withPhishingCheck')
|
||||
->with($runPhishingCheck)
|
||||
->willReturnSelf();
|
||||
$imapMessageFetcher1->expects(self::once())
|
||||
->method('fetchMessage')
|
||||
->with($fetchResult1)
|
||||
|
@ -106,6 +111,10 @@ class MessageMapperTest extends TestCase {
|
|||
->method('withBody')
|
||||
->with($loadBody)
|
||||
->willReturnSelf();
|
||||
$imapMessageFetcher2->expects(self::once())
|
||||
->method('withPhishingCheck')
|
||||
->with($runPhishingCheck)
|
||||
->willReturnSelf();
|
||||
$imapMessageFetcher2->expects(self::once())
|
||||
->method('fetchMessage')
|
||||
->with($fetchResult2)
|
||||
|
@ -167,6 +176,7 @@ class MessageMapperTest extends TestCase {
|
|||
$ids = [1, 3];
|
||||
$userId = 'user';
|
||||
$loadBody = false;
|
||||
$runPhishingCheck = false;
|
||||
|
||||
$imapMessageFetcher1 = $this->createMock(ImapMessageFetcher::class);
|
||||
$message1 = $this->createMock(IMAPMessage::class);
|
||||
|
@ -201,6 +211,10 @@ class MessageMapperTest extends TestCase {
|
|||
->method('withBody')
|
||||
->with($loadBody)
|
||||
->willReturnSelf();
|
||||
$imapMessageFetcher1->expects(self::once())
|
||||
->method('withPhishingCheck')
|
||||
->with($runPhishingCheck)
|
||||
->willReturnSelf();
|
||||
$imapMessageFetcher1->expects(self::once())
|
||||
->method('fetchMessage')
|
||||
->with($fetchResult1)
|
||||
|
|
|
@ -72,6 +72,7 @@ class IMAPMessageTest extends TestCase {
|
|||
'',
|
||||
'disposition',
|
||||
false,
|
||||
[],
|
||||
null,
|
||||
false,
|
||||
'',
|
||||
|
@ -113,6 +114,7 @@ class IMAPMessageTest extends TestCase {
|
|||
'',
|
||||
'disposition',
|
||||
false,
|
||||
[],
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
|
@ -152,6 +154,7 @@ class IMAPMessageTest extends TestCase {
|
|||
'hasHtmlBody' => true,
|
||||
'dispositionNotificationTo' => 'disposition',
|
||||
'hasDkimSignature' => false,
|
||||
'phishingDetails' => [],
|
||||
'scheduling' => [],
|
||||
], $json);
|
||||
$this->assertEquals(1234, $json['uid']);
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Mail\Tests\Unit\Service\Phishing;
|
||||
|
||||
use ChristophWurst\Nextcloud\Testing\TestCase;
|
||||
|
||||
use OCA\Mail\Service\ContactsIntegration;
|
||||
use OCA\Mail\Service\PhishingDetection\ContactCheck;
|
||||
use OCP\IL10N;
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
||||
class ContactCheckTest extends TestCase {
|
||||
|
||||
private IL10N|MockObject $l10n;
|
||||
private ContactsIntegration|MockObject $contactsIntegration;
|
||||
private ContactCheck|MockObject $service;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->contactsIntegration = $this->createMock(ContactsIntegration::class);
|
||||
$this->service = new ContactCheck($this->contactsIntegration, $this->l10n);
|
||||
}
|
||||
|
||||
public function testContactInABCorrectEmail(): void {
|
||||
|
||||
$fn = "John Doe";
|
||||
$email = "jhon@example.com" ;
|
||||
$contacts = [
|
||||
[
|
||||
"email" => ["jhon@example.com"]
|
||||
]
|
||||
];
|
||||
$this->contactsIntegration->method('getContactsWithName')->willReturn($contacts);
|
||||
$result = $this->service->run($fn, $email);
|
||||
|
||||
|
||||
$this->assertFalse($result->isPhishing());
|
||||
}
|
||||
|
||||
public function testContactInABWrongEmail(): void {
|
||||
|
||||
$fn = "John Doe";
|
||||
$email = "jhon@example.com" ;
|
||||
$contacts = [
|
||||
[
|
||||
"email" => ["jhonDoe@example.com"]
|
||||
]
|
||||
];
|
||||
$this->contactsIntegration->method('getContactsWithName')->willReturn($contacts);
|
||||
$result = $this->service->run($fn, $email);
|
||||
|
||||
|
||||
$this->assertTrue($result->isPhishing());
|
||||
}
|
||||
|
||||
public function testContactNotInAB(): void {
|
||||
|
||||
$fn = "John Doe";
|
||||
$email = "jhon@example.com" ;
|
||||
$contacts = [];
|
||||
$this->contactsIntegration->method('getContactsWithName')->willReturn($contacts);
|
||||
$result = $this->service->run($fn, $email);
|
||||
$this->assertFalse($result->isPhishing());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Mail\Tests\Unit\Service\Phishing;
|
||||
|
||||
use ChristophWurst\Nextcloud\Testing\TestCase;
|
||||
|
||||
use OCA\Mail\Service\PhishingDetection\CustomEmailCheck;
|
||||
use OCP\IL10N;
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
||||
class CustomEmailCheckTest extends TestCase {
|
||||
|
||||
private IL10N|MockObject $l10n;
|
||||
private CustomEmailCheck|MockObject $service;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->service = new CustomEmailCheck($this->l10n);
|
||||
}
|
||||
|
||||
public function testNoEmail(): void {
|
||||
$email = "jhon@example.com";
|
||||
$result = $this->service->run($email, null);
|
||||
|
||||
$this->assertFalse($result->isPhishing());
|
||||
}
|
||||
|
||||
public function testSameEmail(): void {
|
||||
$email = "jhon@example.com";
|
||||
$result = $this->service->run($email, $email);
|
||||
|
||||
$this->assertFalse($result->isPhishing());
|
||||
}
|
||||
|
||||
public function testDifferentEmail(): void {
|
||||
$email = "jhon@example.com";
|
||||
$customEmail = "jhondoe@example.com";
|
||||
$result = $this->service->run($email, $customEmail);
|
||||
|
||||
$this->assertTrue($result->isPhishing());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Mail\Tests\Unit\Service\Phishing;
|
||||
|
||||
use ChristophWurst\Nextcloud\Testing\TestCase;
|
||||
|
||||
use DateTime;
|
||||
use OCA\Mail\Service\PhishingDetection\DateCheck;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IL10N;
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
||||
class DateCheckTest extends TestCase {
|
||||
|
||||
private IL10N|MockObject $l10n;
|
||||
private DateCheck|MockObject $service;
|
||||
private ITimeFactory|MockObject $time;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->time = $this->createMock(ITimeFactory::class);
|
||||
$this->service = new DateCheck($this->l10n, $this->time);
|
||||
}
|
||||
|
||||
public function testInThePast(): void {
|
||||
|
||||
$this->time->expects($this->exactly(2))
|
||||
->method('getDateTime')
|
||||
->withConsecutive(
|
||||
['now'],
|
||||
["26 June 2024 22:45:34 +0000"]
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
new DateTime('now'),
|
||||
new DateTime('26 June 2024 22:45:34 +0000')
|
||||
);
|
||||
|
||||
$result = $this->service->run("26 June 2024 22:45:34 +0000");
|
||||
|
||||
$this->assertFalse($result->isPhishing());
|
||||
}
|
||||
|
||||
public function testInTheFuture(): void {
|
||||
|
||||
|
||||
$this->time->expects($this->exactly(2))
|
||||
->method('getDateTime')
|
||||
->withConsecutive(
|
||||
['now'],
|
||||
["17 June 3000 22:45:34 +0000"]
|
||||
)
|
||||
->willReturnOnConsecutiveCalls(
|
||||
new DateTime('now'),
|
||||
new DateTime('17 June 3000 22:45:34 +0000')
|
||||
);
|
||||
|
||||
$result = $this->service->run("17 June 3000 22:45:34 +0000");
|
||||
|
||||
$this->assertTrue($result->isPhishing());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Mail\Tests\Unit\Service\Phishing;
|
||||
|
||||
use ChristophWurst\Nextcloud\Testing\TestCase;
|
||||
use Horde_Mime_Headers;
|
||||
use OCA\Mail\PhishingDetectionResult;
|
||||
|
||||
use OCA\Mail\Service\PhishingDetection\ContactCheck;
|
||||
use OCA\Mail\Service\PhishingDetection\CustomEmailCheck;
|
||||
use OCA\Mail\Service\PhishingDetection\DateCheck;
|
||||
use OCA\Mail\Service\PhishingDetection\PhishingDetectionService;
|
||||
use OCA\Mail\Service\PhishingDetection\ReplyToCheck;
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
||||
class PhishingDetectionServiceTest extends TestCase {
|
||||
|
||||
private ContactCheck|MockObject $contactCheck;
|
||||
private CustomEmailCheck|MockObject $customEmailCheck;
|
||||
private DateCheck|MockObject $dateCheck;
|
||||
private ReplyToCheck|MockObject $replyToCheck;
|
||||
private PhishingDetectionService $service;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->contactCheck = $this->createMock(ContactCheck::class);
|
||||
$this->customEmailCheck = $this->createMock(customEmailCheck::class);
|
||||
$this->dateCheck = $this->createMock(DateCheck::class);
|
||||
$this->replyToCheck = $this->createMock(ReplyToCheck::class);
|
||||
$this->service = new PhishingDetectionService($this->contactCheck, $this->customEmailCheck, $this->dateCheck, $this->replyToCheck);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function testCheckHeadersForPhishing(): void {
|
||||
$headerStream = fopen(__DIR__ . '/../../../data/phishing-mail-headers.txt', 'r');
|
||||
$parsedHeaders = Horde_Mime_Headers::parseHeaders($headerStream);
|
||||
fclose($headerStream);
|
||||
$this->replyToCheck->expects($this->once())
|
||||
->method('run')
|
||||
->with('jhondoe@example.com', 'batman@example.com')
|
||||
->willReturn(new PhishingDetectionResult(PhishingDetectionResult::REPLYTO_CHECK, false));
|
||||
$this->contactCheck->expects($this->once())
|
||||
->method('run')
|
||||
->with('Jhon Doe', 'jhondoe@example.com')
|
||||
->willReturn(new PhishingDetectionResult(PhishingDetectionResult::CONTACTS_CHECK, false));
|
||||
$this->dateCheck->expects($this->once())
|
||||
->method('run')
|
||||
->with('Tue, 28 May 3024 13:02:15 +0200')
|
||||
->willReturn(new PhishingDetectionResult(PhishingDetectionResult::DATE_CHECK, false));
|
||||
$this->customEmailCheck->expects($this->once())
|
||||
->method('run')
|
||||
->willReturn(new PhishingDetectionResult(PhishingDetectionResult::CUSTOM_EMAIL_CHECK, false));
|
||||
$result = $this->service->checkHeadersForPhishing($parsedHeaders, false);
|
||||
$this->assertFalse($result["warning"]);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Mail\Tests\Integration\Service\Phishing;
|
||||
|
||||
use ChristophWurst\Nextcloud\Testing\TestCase;
|
||||
|
||||
use OCA\Mail\Service\PhishingDetection\ReplyToCheck;
|
||||
use OCP\IL10N;
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
||||
class ReplyToCheckTest extends TestCase {
|
||||
|
||||
private IL10N|MockObject $l10n;
|
||||
private ReplyToCheck|MockObject $service;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->service = new ReplyToCheck($this->l10n);
|
||||
}
|
||||
|
||||
|
||||
public function testNoEmail(): void {
|
||||
$email = "jhon@example.com";
|
||||
$result = $this->service->run($email, null);
|
||||
|
||||
$this->assertFalse($result->isPhishing());
|
||||
}
|
||||
|
||||
public function testSameEmail(): void {
|
||||
$email = "jhon@example.com";
|
||||
$result = $this->service->run($email, $email);
|
||||
|
||||
$this->assertFalse($result->isPhishing());
|
||||
}
|
||||
|
||||
public function testDifferentEmail(): void {
|
||||
$email = "jhon@example.com";
|
||||
$customEmail = "jhondoe@example.com";
|
||||
$result = $this->service->run($email, $customEmail);
|
||||
|
||||
$this->assertTrue($result->isPhishing());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
MIME-Version: 1.0
|
||||
Received: from DB3P189MB2377.EURP189.PROD.OUTLOOK.COM (2603:10a6:10:434::18)
|
||||
by HE1P189MB0380.EURP189.PROD.OUTLOOK.COM with HTTPS; Tue, 28 May 2024
|
||||
11:02:30 +0000
|
||||
Received: from BYAPR03CA0019.namprd03.prod.outlook.com (2603:10b6:a02:a8::32)
|
||||
by DB3P189MB2377.EURP189.PROD.OUTLOOK.COM (2603:10a6:10:434::18) with
|
||||
Microsoft SMTP Server (version=TLS1_2,
|
||||
cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7611.29; Tue, 28 May
|
||||
2024 11:02:29 +0000
|
||||
Received: from MWH0EPF000971E4.namprd02.prod.outlook.com
|
||||
(2603:10b6:a02:a8:cafe::b6) by BYAPR03CA0019.outlook.office365.com
|
||||
(2603:10b6:a02:a8::32) with Microsoft SMTP Server (version=TLS1_2,
|
||||
cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7633.17 via Frontend
|
||||
Transport; Tue, 28 May 2024 11:02:27 +0000
|
||||
Authentication-Results: spf=pass (sender IP is 209.85.218.45)
|
||||
smtp.mailfrom=example.com; dkim=pass (signature was verified)
|
||||
header.d=example.com;dmarc=pass action=none header.from=example.com;compauth=pass
|
||||
reason=100
|
||||
Received-SPF: Pass (protection.outlook.com: domain of example.com designates
|
||||
209.85.218.45 as permitted sender) receiver=protection.outlook.com;
|
||||
client-ip=209.85.218.45; helo=mail-ej1-f45.google.com; pr=C
|
||||
Received: from mail-ej1-f45.google.com (209.85.218.45) by
|
||||
MWH0EPF000971E4.mail.protection.outlook.com (10.167.243.72) with Microsoft
|
||||
SMTP Server (version=TLS1_3, cipher=TLS_AES_256_GCM_SHA384) id 15.20.7633.15
|
||||
via Frontend Transport; Tue, 28 May 2024 11:02:27 +0000
|
||||
Received: by mail-ej1-f45.google.com with SMTP id a640c23a62f3a-a626777f74eso85431166b.3
|
||||
for <bob@example.com>; Tue, 28 May 2024 04:02:27 -0700 (PDT)
|
||||
From: Jhon Doe <jhondoe@example.com>
|
||||
Date: Tue, 28 May 3024 13:02:15 +0200
|
||||
Message-ID: <CAOFMF9F1cMTJz=cSEk13i=3xDQ4fbrb5G-z1GWntYFrg+A__Vg@mail.example.com>
|
||||
Subject: Smells Phishy 🎣
|
||||
To: bobby bob <bob@example.com>
|
||||
Content-Type: multipart/alternative; boundary="000000000000b4c1d1061981915d"
|
||||
X-IncomingHeaderCount: 13
|
||||
Return-Path: jhondoe@example.com
|
||||
Reply-To: Jhon Doe <batman@example.com>
|
|
@ -0,0 +1,2 @@
|
|||
SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
Загрузка…
Ссылка в новой задаче