diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index 4d916e43dec14..2eafac731fdfc 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -124,6 +124,18 @@ public function schedule(Message $iTipMessage) { $iTipMessage->scheduleStatus = '5.0; EMail delivery failed'; return; } + + // Check if external attendees are disabled + $externalAttendeesDisabled = $this->config->getValueBool('dav', 'caldav_external_attendees_disabled', false); + if ($externalAttendeesDisabled && !$this->imipService->isSystemUser($recipient)) { + $this->logger->debug('Invitation not sent to external attendee (external attendees disabled)', [ + 'uid' => $iTipMessage->uid, + 'attendee' => $recipient, + ]); + $iTipMessage->scheduleStatus = '5.0; External attendees are disabled'; + return; + } + $recipientName = $iTipMessage->recipientName ? (string)$iTipMessage->recipientName : null; $newEvents = $iTipMessage->message; diff --git a/apps/dav/lib/CalDAV/Schedule/IMipService.php b/apps/dav/lib/CalDAV/Schedule/IMipService.php index ef79ceff4aedd..71e0c2c59c3d6 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipService.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipService.php @@ -873,6 +873,16 @@ public function getLastOccurrence(VCalendar $vObject) { return $dtStart->getDateTime()->getTimeStamp(); } + /** + * Check if an email address belongs to a system user + * + * @param string $email + * @return bool True if the email belongs to a system user, false otherwise + */ + public function isSystemUser(string $email): bool { + return !empty($this->userManager->getByEmail($email)); + } + /** * @param Property $attendee */ diff --git a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php index 896d7e9eb5f16..9f4bd33ab425c 100644 --- a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php +++ b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php @@ -156,6 +156,10 @@ public function testDeliveryNoSignificantChange(): void { $message->senderName = 'Mr. Wizard'; $message->recipient = 'mailto:' . 'frodo@hobb.it'; $message->significantChange = false; + + $this->config->expects(self::never()) + ->method('getValueBool'); + $this->plugin->schedule($message); $this->assertEquals('1.0', $message->getScheduleStatus()); } @@ -203,6 +207,17 @@ public function testParsingSingle(): void { $this->service->expects(self::once()) ->method('getLastOccurrence') ->willReturn(1496912700); + $this->config->expects(self::exactly(2)) + ->method('getValueBool') + ->willReturnCallback(function ($app, $key, $default) { + if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') { + return false; + } + if ($app === 'core' && $key === 'mail_providers_enabled') { + return false; + } + return $default; + }); $this->mailer->expects(self::once()) ->method('validateMailAddress') ->with('frodo@hobb.it') @@ -310,6 +325,10 @@ public function testAttendeeIsResource(): void { $this->service->expects(self::once()) ->method('getLastOccurrence') ->willReturn(1496912700); + $this->config->expects(self::once()) + ->method('getValueBool') + ->with('dav', 'caldav_external_attendees_disabled', false) + ->willReturn(false); $this->mailer->expects(self::once()) ->method('validateMailAddress') ->with('the-shire@hobb.it') @@ -388,6 +407,10 @@ public function testAttendeeIsCircle(): void { $this->service->expects(self::once()) ->method('getLastOccurrence') ->willReturn(1496912700); + $this->config->expects(self::once()) + ->method('getValueBool') + ->with('dav', 'caldav_external_attendees_disabled', false) + ->willReturn(false); $this->mailer->expects(self::once()) ->method('validateMailAddress') ->with('circle+82utEV1Fle8wvxndZLK5TVAPtxj8IIe@middle.earth') @@ -493,6 +516,17 @@ public function testParsingRecurrence(): void { $this->service->expects(self::once()) ->method('getLastOccurrence') ->willReturn(1496912700); + $this->config->expects(self::exactly(2)) + ->method('getValueBool') + ->willReturnCallback(function ($app, $key, $default) { + if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') { + return false; + } + if ($app === 'core' && $key === 'mail_providers_enabled') { + return false; + } + return $default; + }); $this->mailer->expects(self::once()) ->method('validateMailAddress') ->with('frodo@hobb.it') @@ -745,6 +779,17 @@ public function testMailProviderSend(): void { $this->service->expects(self::once()) ->method('getLastOccurrence') ->willReturn(1496912700); + $this->config->expects(self::exactly(2)) + ->method('getValueBool') + ->willReturnCallback(function ($app, $key, $default) { + if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') { + return false; + } + if ($app === 'core' && $key === 'mail_providers_enabled') { + return true; + } + return $default; + }); $this->service->expects(self::once()) ->method('getCurrentAttendee') ->with($message) @@ -896,10 +941,17 @@ public function testMailProviderDisabled(): void { ->method('getValueString') ->with('dav', 'invitation_link_recipients', 'yes') ->willReturn('yes'); - $this->config->expects(self::once()) + $this->config->expects(self::exactly(2)) ->method('getValueBool') - ->with('core', 'mail_providers_enabled', true) - ->willReturn(false); + ->willReturnCallback(function ($app, $key, $default) { + if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') { + return false; + } + if ($app === 'core' && $key === 'mail_providers_enabled') { + return false; + } + return $default; + }); $this->service->expects(self::once()) ->method('createInvitationToken') ->with($message, $newVevent, 1496912700) @@ -947,6 +999,17 @@ public function testNoOldEvent(): void { $this->service->expects(self::once()) ->method('getLastOccurrence') ->willReturn(1496912700); + $this->config->expects(self::exactly(2)) + ->method('getValueBool') + ->willReturnCallback(function ($app, $key, $default) { + if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') { + return false; + } + if ($app === 'core' && $key === 'mail_providers_enabled') { + return false; + } + return $default; + }); $this->mailer->expects(self::once()) ->method('validateMailAddress') ->with('frodo@hobb.it') @@ -1044,6 +1107,17 @@ public function testNoButtons(): void { $this->service->expects(self::once()) ->method('getLastOccurrence') ->willReturn(1496912700); + $this->config->expects(self::exactly(2)) + ->method('getValueBool') + ->willReturnCallback(function ($app, $key, $default) { + if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') { + return false; + } + if ($app === 'core' && $key === 'mail_providers_enabled') { + return false; + } + return $default; + }); $this->mailer->expects(self::once()) ->method('validateMailAddress') ->with('frodo@hobb.it') @@ -1107,4 +1181,167 @@ public function testNoButtons(): void { $this->plugin->schedule($message); $this->assertEquals('1.1', $message->getScheduleStatus()); } + + public function testExternalAttendeesDisabledForExternalUser(): 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', + 'DTSTART' => new \DateTime('2016-01-01 00:00:00') + ], [])); + $newVevent->add('ORGANIZER', 'mailto:gandalf@wiz.ard'); + $newVevent->add('ATTENDEE', 'mailto:external@example.com', ['RSVP' => 'TRUE', 'CN' => 'External User']); + $message->message = $newVCalendar; + $message->sender = 'mailto:gandalf@wiz.ard'; + $message->senderName = 'Mr. Wizard'; + $message->recipient = 'mailto:external@example.com'; + + $this->service->expects(self::once()) + ->method('getLastOccurrence') + ->willReturn(1496912700); + $this->config->expects(self::once()) + ->method('getValueBool') + ->with('dav', 'caldav_external_attendees_disabled', false) + ->willReturn(true); + $this->service->expects(self::once()) + ->method('isSystemUser') + ->with('external@example.com') + ->willReturn(false); + $this->eventComparisonService->expects(self::never()) + ->method('findModified'); + $this->service->expects(self::never()) + ->method('getCurrentAttendee'); + $this->mailer->expects(self::once()) + ->method('validateMailAddress') + ->willReturn(true); + $this->mailer->expects(self::never()) + ->method('send'); + + $this->plugin->schedule($message); + $this->assertEquals('5.0', $message->getScheduleStatus()); + } + + public function testExternalAttendeesDisabledForSystemUser(): 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', + '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'; + + $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']); + $oldVCalendar->add($oldVEvent); + + $data = ['invitee_name' => 'Mr. Wizard', + 'meeting_title' => 'Fellowship meeting', + '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->config->expects(self::exactly(2)) + ->method('getValueBool') + ->willReturnCallback(function ($app, $key, $default) { + if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') { + return true; + } + if ($app === 'core' && $key === 'mail_providers_enabled') { + return false; + } + return $default; + }); + $this->service->expects(self::once()) + ->method('isSystemUser') + ->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('isCircle') + ->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', 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->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('validateMailAddress') + ->willReturn(true); + $this->mailer->expects(self::once()) + ->method('send') + ->willReturn([]); + $this->plugin->schedule($message); + $this->assertEquals('1.1', $message->getScheduleStatus()); + } } diff --git a/apps/dav/tests/unit/CalDAV/Schedule/IMipServiceTest.php b/apps/dav/tests/unit/CalDAV/Schedule/IMipServiceTest.php index 141a722f30a52..6255d7640df83 100644 --- a/apps/dav/tests/unit/CalDAV/Schedule/IMipServiceTest.php +++ b/apps/dav/tests/unit/CalDAV/Schedule/IMipServiceTest.php @@ -181,6 +181,31 @@ public function testGetFrom(): void { $this->assertEquals($expected, $actual); } + public function testIsSystemUserWhenUserExists(): void { + $email = 'user@example.com'; + $user = $this->createMock(\OCP\IUser::class); + + $this->userManager->expects(self::once()) + ->method('getByEmail') + ->with($email) + ->willReturn([$user]); + + $result = $this->service->isSystemUser($email); + $this->assertTrue($result); + } + + public function testIsSystemUserWhenUserDoesNotExist(): void { + $email = 'external@example.com'; + + $this->userManager->expects(self::once()) + ->method('getByEmail') + ->with($email) + ->willReturn([]); + + $result = $this->service->isSystemUser($email); + $this->assertFalse($result); + } + public function testBuildBodyDataCreated(): void { // construct l10n return(s)