From 1260ec5f21b84211a795ff03254be5a49eff40c1 Mon Sep 17 00:00:00 2001 From: SebastianKrupinski Date: Tue, 17 Sep 2024 07:45:44 -0400 Subject: feat: mail provider settings Signed-off-by: SebastianKrupinski --- apps/dav/lib/CalDAV/Schedule/IMipPlugin.php | 22 ++-- apps/dav/lib/Server.php | 3 +- .../tests/unit/CalDAV/Schedule/IMipPluginTest.php | 127 +++++++++++++++++++-- .../composer/composer/autoload_classmap.php | 2 + .../settings/composer/composer/autoload_static.php | 2 + apps/settings/lib/AppInfo/Application.php | 11 ++ .../settings/lib/Listener/MailProviderListener.php | 61 ++++++++++ apps/settings/lib/Settings/Admin/MailProvider.php | 52 +++++++++ 8 files changed, 259 insertions(+), 21 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 1cfe8f47cb5..8ed3cf4d56f 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\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) { diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index 55c8a5afec1..f4b8dcaf3a1 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -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), diff --git a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php index c8dfe257bf2..b459b9da7c8 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\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()) diff --git a/apps/settings/composer/composer/autoload_classmap.php b/apps/settings/composer/composer/autoload_classmap.php index 41f70c3a8e6..58020673a1c 100644 --- a/apps/settings/composer/composer/autoload_classmap.php +++ b/apps/settings/composer/composer/autoload_classmap.php @@ -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', diff --git a/apps/settings/composer/composer/autoload_static.php b/apps/settings/composer/composer/autoload_static.php index 4fa905b55bb..e500cd19d61 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 80420cb3335..0d71d9c67ef 100644 --- a/apps/settings/lib/AppInfo/Application.php +++ b/apps/settings/lib/AppInfo/Application.php @@ -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 */ diff --git a/apps/settings/lib/Listener/MailProviderListener.php b/apps/settings/lib/Listener/MailProviderListener.php new file mode 100644 index 00000000000..974378c0006 --- /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 00000000000..edbb484b16e --- /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 + ], + ], + ], + ], + ]; + } + +} -- cgit v1.2.3