feat: mail provider settings

Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
This commit is contained in:
SebastianKrupinski 2024-09-17 07:45:44 -04:00
parent aba18bacb3
commit 1260ec5f21
8 changed files with 259 additions and 21 deletions

View file

@ -12,7 +12,7 @@ use OCA\DAV\CalDAV\CalendarObject;
use OCA\DAV\CalDAV\EventComparisonService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Defaults;
use OCP\IConfig;
use OCP\IAppConfig;
use OCP\IUserSession;
use OCP\Mail\IMailer;
use OCP\Mail\Provider\IManager as IMailManager;
@ -44,7 +44,7 @@ use Sabre\VObject\Reader;
*/
class IMipPlugin extends SabreIMipPlugin {
private IUserSession $userSession;
private IConfig $config;
private IAppConfig $config;
private IMailer $mailer;
private LoggerInterface $logger;
private ITimeFactory $timeFactory;
@ -59,7 +59,8 @@ class IMipPlugin extends SabreIMipPlugin {
private EventComparisonService $eventComparisonService;
private IMailManager $mailManager;
public function __construct(IConfig $config,
public function __construct(
IAppConfig $config,
IMailer $mailer,
LoggerInterface $logger,
ITimeFactory $timeFactory,
@ -254,7 +255,7 @@ class IMipPlugin extends SabreIMipPlugin {
*/
$recipientDomain = substr(strrchr($recipient, '@'), 1);
$invitationLinkRecipients = explode(',', preg_replace('/\s+/', '', strtolower($this->config->getAppValue('dav', 'invitation_link_recipients', 'yes'))));
$invitationLinkRecipients = explode(',', preg_replace('/\s+/', '', strtolower($this->config->getValueString('dav', 'invitation_link_recipients', 'yes'))));
if (strcmp('yes', $invitationLinkRecipients[0]) === 0
|| in_array(strtolower($recipient), $invitationLinkRecipients)
@ -273,12 +274,13 @@ class IMipPlugin extends SabreIMipPlugin {
$mailService = null;
try {
// retrieve user object
$user = $this->userSession->getUser();
// evaluate if user object exist
if ($user !== null) {
// retrieve appropriate service with the same address as sender
$mailService = $this->mailManager->findServiceByAddress($user->getUID(), $sender);
if ($this->config->getValueBool('core', 'mail_providers_enabled', true)) {
// retrieve user object
$user = $this->userSession->getUser();
if ($user !== null) {
// retrieve appropriate service with the same address as sender
$mailService = $this->mailManager->findServiceByAddress($user->getUID(), $sender);
}
}
// evaluate if a mail service was found and has sending capabilities
if ($mailService !== null && $mailService instanceof IMessageSend) {

View file

@ -55,6 +55,7 @@ use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\IFilenameValidator;
use OCP\FilesMetadata\IFilesMetadataManager;
use OCP\IAppConfig;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IPreview;
@ -289,7 +290,7 @@ class Server {
));
if (\OC::$server->getConfig()->getAppValue('dav', 'sendInvitations', 'yes') === 'yes') {
$this->server->addPlugin(new IMipPlugin(
\OC::$server->get(\OCP\IConfig::class),
\OC::$server->get(IAppConfig::class),
\OC::$server->get(\OCP\Mail\IMailer::class),
\OC::$server->get(LoggerInterface::class),
\OC::$server->get(\OCP\AppFramework\Utility\ITimeFactory::class),

View file

@ -11,7 +11,7 @@ use OCA\DAV\CalDAV\Schedule\IMipPlugin;
use OCA\DAV\CalDAV\Schedule\IMipService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Defaults;
use OCP\IConfig;
use OCP\IAppConfig;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Mail\IAttachment;
@ -52,7 +52,7 @@ class IMipPluginTest extends TestCase {
/** @var ITimeFactory|MockObject */
private $timeFactory;
/** @var IConfig|MockObject */
/** @var IAppConfig|MockObject */
private $config;
/** @var IUserSession|MockObject */
@ -105,7 +105,7 @@ class IMipPluginTest extends TestCase {
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->timeFactory->method('getTime')->willReturn(1496912528); // 2017-01-01
$this->config = $this->createMock(IConfig::class);
$this->config = $this->createMock(IAppConfig::class);
$this->user = $this->createMock(IUser::class);
@ -243,7 +243,7 @@ class IMipPluginTest extends TestCase {
->method('getAttendeeRsvpOrReqForParticipant')
->willReturn(true);
$this->config->expects(self::once())
->method('getAppValue')
->method('getValueString')
->with('dav', 'invitation_link_recipients', 'yes')
->willReturn('yes');
$this->service->expects(self::once())
@ -341,7 +341,7 @@ class IMipPluginTest extends TestCase {
$this->service->expects(self::never())
->method('getAttendeeRsvpOrReqForParticipant');
$this->config->expects(self::never())
->method('getAppValue');
->method('getValueString');
$this->service->expects(self::never())
->method('createInvitationToken');
$this->service->expects(self::never())
@ -447,7 +447,7 @@ class IMipPluginTest extends TestCase {
->method('getAttendeeRsvpOrReqForParticipant')
->willReturn(true);
$this->config->expects(self::once())
->method('getAppValue')
->method('getValueString')
->with('dav', 'invitation_link_recipients', 'yes')
->willReturn('yes');
$this->service->expects(self::once())
@ -578,7 +578,7 @@ class IMipPluginTest extends TestCase {
->method('getAttendeeRsvpOrReqForParticipant')
->willReturn(true);
$this->config->expects(self::once())
->method('getAppValue')
->method('getValueString')
->with('dav', 'invitation_link_recipients', 'yes')
->willReturn('yes');
$this->service->expects(self::once())
@ -633,7 +633,7 @@ class IMipPluginTest extends TestCase {
];
// construct system config mock returns
$this->config->expects(self::once())
->method('getAppValue')
->method('getValueString')
->with('dav', 'invitation_link_recipients', 'yes')
->willReturn('yes');
// construct user mock returns
@ -708,6 +708,113 @@ class IMipPluginTest extends TestCase {
$this->assertEquals('1.1', $message->getScheduleStatus());
}
public function testMailProviderDisabled(): void {
$message = new Message();
$message->method = 'REQUEST';
$newVCalendar = new VCalendar();
$newVevent = new VEvent($newVCalendar, 'one', array_merge([
'UID' => 'uid-1234',
'SEQUENCE' => 1,
'SUMMARY' => 'Fellowship meeting without (!) Boromir',
'DTSTART' => new \DateTime('2016-01-01 00:00:00')
], []));
$newVevent->add('ORGANIZER', 'mailto:gandalf@wiz.ard');
$newVevent->add('ATTENDEE', 'mailto:' . 'frodo@hobb.it', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
$message->message = $newVCalendar;
$message->sender = 'mailto:gandalf@wiz.ard';
$message->senderName = 'Mr. Wizard';
$message->recipient = 'mailto:' . 'frodo@hobb.it';
// save the old copy in the plugin
$oldVCalendar = new VCalendar();
$oldVEvent = new VEvent($oldVCalendar, 'one', [
'UID' => 'uid-1234',
'SEQUENCE' => 0,
'SUMMARY' => 'Fellowship meeting',
'DTSTART' => new \DateTime('2016-01-01 00:00:00')
]);
$oldVEvent->add('ORGANIZER', 'mailto:gandalf@wiz.ard');
$oldVEvent->add('ATTENDEE', 'mailto:' . 'frodo@hobb.it', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
$oldVEvent->add('ATTENDEE', 'mailto:' . 'boromir@tra.it.or', ['RSVP' => 'TRUE']);
$oldVCalendar->add($oldVEvent);
$data = ['invitee_name' => 'Mr. Wizard',
'meeting_title' => 'Fellowship meeting without (!) Boromir',
'attendee_name' => 'frodo@hobb.it'
];
$attendees = $newVevent->select('ATTENDEE');
$atnd = '';
foreach ($attendees as $attendee) {
if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
$atnd = $attendee;
}
}
$this->plugin->setVCalendar($oldVCalendar);
$this->service->expects(self::once())
->method('getLastOccurrence')
->willReturn(1496912700);
$this->mailer->expects(self::once())
->method('validateMailAddress')
->with('frodo@hobb.it')
->willReturn(true);
$this->eventComparisonService->expects(self::once())
->method('findModified')
->willReturn(['new' => [$newVevent], 'old' => [$oldVEvent]]);
$this->service->expects(self::once())
->method('getCurrentAttendee')
->with($message)
->willReturn($atnd);
$this->service->expects(self::once())
->method('isRoomOrResource')
->with($atnd)
->willReturn(false);
$this->service->expects(self::once())
->method('buildBodyData')
->with($newVevent, $oldVEvent)
->willReturn($data);
$this->user->expects(self::any())
->method('getUID')
->willReturn('user1');
$this->user->expects(self::any())
->method('getDisplayName')
->willReturn('Mr. Wizard');
$this->userSession->expects(self::any())
->method('getUser')
->willReturn($this->user);
$this->service->expects(self::once())
->method('getFrom');
$this->service->expects(self::once())
->method('addSubjectAndHeading')
->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting without (!) Boromir', true);
$this->service->expects(self::once())
->method('addBulletList')
->with($this->emailTemplate, $newVevent, $data);
$this->service->expects(self::once())
->method('getAttendeeRsvpOrReqForParticipant')
->willReturn(true);
$this->config->expects(self::once())
->method('getValueString')
->with('dav', 'invitation_link_recipients', 'yes')
->willReturn('yes');
$this->config->expects(self::once())
->method('getValueBool')
->with('core', 'mail_providers_enabled', true)
->willReturn(false);
$this->service->expects(self::once())
->method('createInvitationToken')
->with($message, $newVevent, 1496912700)
->willReturn('token');
$this->service->expects(self::once())
->method('addResponseButtons')
->with($this->emailTemplate, 'token');
$this->service->expects(self::once())
->method('addMoreOptionsButton')
->with($this->emailTemplate, 'token');
$this->mailer->expects(self::once())
->method('send')
->willReturn([]);
$this->plugin->schedule($message);
$this->assertEquals('1.1', $message->getScheduleStatus());
}
public function testNoOldEvent(): void {
$message = new Message();
$message->method = 'REQUEST';
@ -779,7 +886,7 @@ class IMipPluginTest extends TestCase {
->method('getAttendeeRsvpOrReqForParticipant')
->willReturn(true);
$this->config->expects(self::once())
->method('getAppValue')
->method('getValueString')
->with('dav', 'invitation_link_recipients', 'yes')
->willReturn('yes');
$this->service->expects(self::once())
@ -872,7 +979,7 @@ class IMipPluginTest extends TestCase {
->method('getAttendeeRsvpOrReqForParticipant')
->willReturn(true);
$this->config->expects(self::once())
->method('getAppValue')
->method('getValueString')
->with('dav', 'invitation_link_recipients', 'yes')
->willReturn('no');
$this->service->expects(self::never())

View file

@ -40,6 +40,7 @@ return array(
'OCA\\Settings\\Hooks' => $baseDir . '/../lib/Hooks.php',
'OCA\\Settings\\Listener\\AppPasswordCreatedActivityListener' => $baseDir . '/../lib/Listener/AppPasswordCreatedActivityListener.php',
'OCA\\Settings\\Listener\\GroupRemovedListener' => $baseDir . '/../lib/Listener/GroupRemovedListener.php',
'OCA\\Settings\\Listener\\MailProviderListener' => $baseDir . '/../lib/Listener/MailProviderListener.php',
'OCA\\Settings\\Listener\\UserAddedToGroupActivityListener' => $baseDir . '/../lib/Listener/UserAddedToGroupActivityListener.php',
'OCA\\Settings\\Listener\\UserRemovedFromGroupActivityListener' => $baseDir . '/../lib/Listener/UserRemovedFromGroupActivityListener.php',
'OCA\\Settings\\Mailer\\NewUserMailHelper' => $baseDir . '/../lib/Mailer/NewUserMailHelper.php',
@ -67,6 +68,7 @@ return array(
'OCA\\Settings\\Settings\\Admin\\ArtificialIntelligence' => $baseDir . '/../lib/Settings/Admin/ArtificialIntelligence.php',
'OCA\\Settings\\Settings\\Admin\\Delegation' => $baseDir . '/../lib/Settings/Admin/Delegation.php',
'OCA\\Settings\\Settings\\Admin\\Mail' => $baseDir . '/../lib/Settings/Admin/Mail.php',
'OCA\\Settings\\Settings\\Admin\\MailProvider' => $baseDir . '/../lib/Settings/Admin/MailProvider.php',
'OCA\\Settings\\Settings\\Admin\\Overview' => $baseDir . '/../lib/Settings/Admin/Overview.php',
'OCA\\Settings\\Settings\\Admin\\Security' => $baseDir . '/../lib/Settings/Admin/Security.php',
'OCA\\Settings\\Settings\\Admin\\Server' => $baseDir . '/../lib/Settings/Admin/Server.php',

View file

@ -55,6 +55,7 @@ class ComposerStaticInitSettings
'OCA\\Settings\\Hooks' => __DIR__ . '/..' . '/../lib/Hooks.php',
'OCA\\Settings\\Listener\\AppPasswordCreatedActivityListener' => __DIR__ . '/..' . '/../lib/Listener/AppPasswordCreatedActivityListener.php',
'OCA\\Settings\\Listener\\GroupRemovedListener' => __DIR__ . '/..' . '/../lib/Listener/GroupRemovedListener.php',
'OCA\\Settings\\Listener\\MailProviderListener' => __DIR__ . '/..' . '/../lib/Listener/MailProviderListener.php',
'OCA\\Settings\\Listener\\UserAddedToGroupActivityListener' => __DIR__ . '/..' . '/../lib/Listener/UserAddedToGroupActivityListener.php',
'OCA\\Settings\\Listener\\UserRemovedFromGroupActivityListener' => __DIR__ . '/..' . '/../lib/Listener/UserRemovedFromGroupActivityListener.php',
'OCA\\Settings\\Mailer\\NewUserMailHelper' => __DIR__ . '/..' . '/../lib/Mailer/NewUserMailHelper.php',
@ -82,6 +83,7 @@ class ComposerStaticInitSettings
'OCA\\Settings\\Settings\\Admin\\ArtificialIntelligence' => __DIR__ . '/..' . '/../lib/Settings/Admin/ArtificialIntelligence.php',
'OCA\\Settings\\Settings\\Admin\\Delegation' => __DIR__ . '/..' . '/../lib/Settings/Admin/Delegation.php',
'OCA\\Settings\\Settings\\Admin\\Mail' => __DIR__ . '/..' . '/../lib/Settings/Admin/Mail.php',
'OCA\\Settings\\Settings\\Admin\\MailProvider' => __DIR__ . '/..' . '/../lib/Settings/Admin/MailProvider.php',
'OCA\\Settings\\Settings\\Admin\\Overview' => __DIR__ . '/..' . '/../lib/Settings/Admin/Overview.php',
'OCA\\Settings\\Settings\\Admin\\Security' => __DIR__ . '/..' . '/../lib/Settings/Admin/Security.php',
'OCA\\Settings\\Settings\\Admin\\Server' => __DIR__ . '/..' . '/../lib/Settings/Admin/Server.php',

View file

@ -15,6 +15,7 @@ use OC\Server;
use OCA\Settings\Hooks;
use OCA\Settings\Listener\AppPasswordCreatedActivityListener;
use OCA\Settings\Listener\GroupRemovedListener;
use OCA\Settings\Listener\MailProviderListener;
use OCA\Settings\Listener\UserAddedToGroupActivityListener;
use OCA\Settings\Listener\UserRemovedFromGroupActivityListener;
use OCA\Settings\Mailer\NewUserMailHelper;
@ -22,6 +23,7 @@ use OCA\Settings\Middleware\SubadminMiddleware;
use OCA\Settings\Search\AppSearch;
use OCA\Settings\Search\SectionSearch;
use OCA\Settings\Search\UserSearch;
use OCA\Settings\Settings\Admin\MailProvider;
use OCA\Settings\SetupChecks\AllowedAdminRanges;
use OCA\Settings\SetupChecks\AppDirsWithDifferentOwner;
use OCA\Settings\SetupChecks\BruteForceThrottler;
@ -83,6 +85,8 @@ use OCP\Group\Events\GroupDeletedEvent;
use OCP\Group\Events\UserAddedEvent;
use OCP\Group\Events\UserRemovedEvent;
use OCP\IServerContainer;
use OCP\Settings\Events\DeclarativeSettingsGetValueEvent;
use OCP\Settings\Events\DeclarativeSettingsSetValueEvent;
use OCP\Settings\IManager;
use OCP\Util;
@ -110,10 +114,17 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(UserRemovedEvent::class, UserRemovedFromGroupActivityListener::class);
$context->registerEventListener(GroupDeletedEvent::class, GroupRemovedListener::class);
// Register Mail Provider listeners
$context->registerEventListener(DeclarativeSettingsGetValueEvent::class, MailProviderListener::class);
$context->registerEventListener(DeclarativeSettingsSetValueEvent::class, MailProviderListener::class);
// Register well-known handlers
$context->registerWellKnownHandler(SecurityTxtHandler::class);
$context->registerWellKnownHandler(ChangePasswordHandler::class);
// Register Settings Form(s)
$context->registerDeclarativeSettings(MailProvider::class);
/**
* Core class wrappers
*/

View file

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Settings\Listener;
use OCA\Settings\AppInfo\Application;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\IAppConfig;
use OCP\Settings\Events\DeclarativeSettingsGetValueEvent;
use OCP\Settings\Events\DeclarativeSettingsSetValueEvent;
/** @template-implements IEventListener<DeclarativeSettingsGetValueEvent|DeclarativeSettingsSetValueEvent> */
class MailProviderListener implements IEventListener {
public function __construct(
private IAppConfig $config,
) {
}
public function handle(Event $event): void {
/** @var DeclarativeSettingsGetValueEvent|DeclarativeSettingsSetValueEvent $event */
if ($event->getApp() !== Application::APP_ID) {
return;
}
if ($event instanceof DeclarativeSettingsGetValueEvent) {
$this->handleGetValue($event);
return;
}
if ($event instanceof DeclarativeSettingsSetValueEvent) {
$this->handleSetValue($event);
return;
}
}
private function handleGetValue(DeclarativeSettingsGetValueEvent $event): void {
if ($event->getFieldId() === 'mail_providers_enabled') {
$event->setValue((int)$this->config->getValueBool('core', 'mail_providers_enabled', true));
}
}
private function handleSetValue(DeclarativeSettingsSetValueEvent $event): void {
if ($event->getFieldId() === 'mail_providers_enabled') {
$this->config->setValueBool('core', 'mail_providers_enabled', (bool)$event->getValue());
$event->stopPropagation();
}
}
}

View file

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Settings\Settings\Admin;
use OCP\IL10N;
use OCP\Settings\DeclarativeSettingsTypes;
use OCP\Settings\IDeclarativeSettingsForm;
class MailProvider implements IDeclarativeSettingsForm {
public function __construct(
private IL10N $l,
) {
}
public function getSchema(): array {
return [
'id' => 'mail-provider-support',
'priority' => 10,
'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN,
'section_id' => 'server',
'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL,
'title' => $this->l->t('Mail Providers'),
'description' => $this->l->t('Mail provider enables sending emails directly through the user\'s personal email account. At present, this functionality is limited to calendar invitations. It requires Nextcloud Mail 4.1 and an email account in Nextcloud Mail that matches the user\'s email address in Nextcloud.'),
'fields' => [
[
'id' => 'mail_providers_enabled',
'title' => $this->l->t('Send emails using'),
'type' => DeclarativeSettingsTypes::RADIO,
'default' => 1,
'options' => [
[
'name' => $this->l->t('Users\'s email account'),
'value' => 1
],
[
'name' => $this->l->t('System email account'),
'value' => 0
],
],
],
],
];
}
}