From 3e870695bc4989e5e2c6f59c44ed5dbd501a7673 Mon Sep 17 00:00:00 2001 From: SebastianKrupinski Date: Tue, 17 Sep 2024 07:45:44 -0400 Subject: [PATCH] feat: mail provider settings Signed-off-by: SebastianKrupinski --- apps/dav/lib/CalDAV/Schedule/IMipPlugin.php | 20 +-- apps/dav/lib/Server.php | 3 +- .../unit/CalDAV/Schedule/IMipPluginTest.php | 127 ++++++++++++++++-- .../composer/composer/autoload_classmap.php | 2 + .../composer/composer/autoload_static.php | 2 + apps/settings/lib/AppInfo/Application.php | 11 ++ .../lib/Listener/MailProviderListener.php | 61 +++++++++ .../lib/Settings/Admin/MailProvider.php | 52 +++++++ 8 files changed, 258 insertions(+), 20 deletions(-) create mode 100644 apps/settings/lib/Listener/MailProviderListener.php create mode 100644 apps/settings/lib/Settings/Admin/MailProvider.php diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index 084d72019337e..d81d85573fc01 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -12,7 +12,7 @@ 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\Address; @@ -45,6 +45,7 @@ * @license http://sabre.io/license/ Modified BSD License */ class IMipPlugin extends SabreIMipPlugin { + private ?VCalendar $vCalendar = null; public const MAX_DATE = '2038-01-01'; public const METHOD_REQUEST = 'request'; @@ -53,7 +54,7 @@ class IMipPlugin extends SabreIMipPlugin { public const IMIP_INDENT = 15; public function __construct( - private IConfig $config, + private IAppConfig $config, private IMailer $mailer, private LoggerInterface $logger, private ITimeFactory $timeFactory, @@ -240,7 +241,7 @@ public function schedule(Message $iTipMessage) { */ $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) @@ -259,12 +260,13 @@ public function schedule(Message $iTipMessage) { $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) { diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index 835a13a45b26e..0dfdd43bf0cb9 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -70,6 +70,7 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\IFilenameValidator; use OCP\FilesMetadata\IFilesMetadataManager; +use OCP\IAppConfig; use OCP\ICacheFactory; use OCP\IConfig; use OCP\IPreview; @@ -311,7 +312,7 @@ public function __construct( )); if (\OC::$server->getConfig()->getAppValue('dav', 'sendInvitations', 'yes') === 'yes') { $this->server->addPlugin(new IMipPlugin( - \OC::$server->get(IConfig::class), + \OC::$server->get(IAppConfig::class), \OC::$server->get(IMailer::class), \OC::$server->get(LoggerInterface::class), \OC::$server->get(ITimeFactory::class), diff --git a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php index 18208981038f6..94c39e42d16af 100644 --- a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php +++ b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php @@ -11,7 +11,7 @@ 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 @@ protected function setUp(): void { $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 @@ public function testParsingSingle(): void { ->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 @@ public function testAttendeeIsResource(): void { $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 @@ public function testParsingRecurrence(): void { ->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 @@ public function testFailedDelivery(): void { ->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 @@ public function testMailProviderSend(): void { ]; // 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 @@ public function testMailProviderSend(): void { $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 @@ public function testNoOldEvent(): void { ->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 @@ public function testNoButtons(): void { ->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()) diff --git a/apps/settings/composer/composer/autoload_classmap.php b/apps/settings/composer/composer/autoload_classmap.php index 56f67d031cbfa..79ea28b66d0be 100644 --- a/apps/settings/composer/composer/autoload_classmap.php +++ b/apps/settings/composer/composer/autoload_classmap.php @@ -40,6 +40,7 @@ '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 @@ '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', diff --git a/apps/settings/composer/composer/autoload_static.php b/apps/settings/composer/composer/autoload_static.php index 4481a8c2142b8..c7e06f2e359df 100644 --- a/apps/settings/composer/composer/autoload_static.php +++ b/apps/settings/composer/composer/autoload_static.php @@ -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', diff --git a/apps/settings/lib/AppInfo/Application.php b/apps/settings/lib/AppInfo/Application.php index d3d12a6c53d3c..688e6ebbed5cd 100644 --- a/apps/settings/lib/AppInfo/Application.php +++ b/apps/settings/lib/AppInfo/Application.php @@ -15,6 +15,7 @@ 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\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; @@ -86,6 +88,8 @@ 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; @@ -113,10 +117,17 @@ public function register(IRegistrationContext $context): void { $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 */ diff --git a/apps/settings/lib/Listener/MailProviderListener.php b/apps/settings/lib/Listener/MailProviderListener.php new file mode 100644 index 0000000000000..974378c0006ce --- /dev/null +++ b/apps/settings/lib/Listener/MailProviderListener.php @@ -0,0 +1,61 @@ + */ +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(); + } + + } + +} diff --git a/apps/settings/lib/Settings/Admin/MailProvider.php b/apps/settings/lib/Settings/Admin/MailProvider.php new file mode 100644 index 0000000000000..edbb484b16ee8 --- /dev/null +++ b/apps/settings/lib/Settings/Admin/MailProvider.php @@ -0,0 +1,52 @@ + '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 + ], + ], + ], + ], + ]; + } + +}