diff options
author | Richard Steinmetz <richard@steinmetz.cloud> | 2025-05-21 13:20:32 +0200 |
---|---|---|
committer | Andy Scherzinger <info@andy-scherzinger.de> | 2025-05-25 23:04:30 +0200 |
commit | d45b4c97f0557b59530b3e1b3f086f4368c5aef1 (patch) | |
tree | 222ace4548af07b8671336515859b2490fbe367b | |
parent | da30e9d1051d9be992b2799cd3dc0a50de67e025 (diff) | |
download | nextcloud-server-backport/53029/stable30.tar.gz nextcloud-server-backport/53029/stable30.zip |
fix(caldav): don't send invitations to circlesbackport/53029/stable30
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
-rw-r--r-- | apps/dav/lib/CalDAV/Schedule/IMipPlugin.php | 7 | ||||
-rw-r--r-- | apps/dav/lib/CalDAV/Schedule/IMipService.php | 15 | ||||
-rw-r--r-- | apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php | 110 |
3 files changed, 129 insertions, 3 deletions
diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index 2e36bda95f1..2f3f101e0ba 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -170,9 +170,10 @@ class IMipPlugin extends SabreIMipPlugin { $iTipMessage->scheduleStatus = '5.0; EMail delivery failed'; return; } - // Don't send emails to things - if ($this->imipService->isRoomOrResource($attendee)) { - $this->logger->debug('No invitation sent as recipient is room or resource', [ + // Don't send emails to rooms, resources and circles + if ($this->imipService->isRoomOrResource($attendee) + || $this->imipService->isCircle($attendee)) { + $this->logger->debug('No invitation sent as recipient is room, resource or circle', [ 'attendee' => $recipient, ]); $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email'; diff --git a/apps/dav/lib/CalDAV/Schedule/IMipService.php b/apps/dav/lib/CalDAV/Schedule/IMipService.php index 137fb3585a3..eb83ebb777f 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipService.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipService.php @@ -1165,6 +1165,21 @@ class IMipService { return false; } + public function isCircle(Property $attendee): bool { + $cuType = $attendee->offsetGet('CUTYPE'); + if (!$cuType instanceof Parameter) { + return false; + } + + $uri = $attendee->getValue(); + if (!$uri) { + return false; + } + + $cuTypeValue = $cuType->getValue(); + return $cuTypeValue === 'GROUP' && str_starts_with($uri, 'mailto:circle+'); + } + public function minimizeInterval(\DateInterval $dateInterval): array { // evaluate if time interval is in the past if ($dateInterval->invert == 1) { diff --git a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php index 3acbf670d17..4fd62b445e3 100644 --- a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php +++ b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php @@ -220,6 +220,10 @@ class IMipPluginTest extends TestCase { ->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); @@ -323,6 +327,88 @@ class IMipPluginTest extends TestCase { ->with($room) ->willReturn(true); $this->service->expects(self::never()) + ->method('isCircle'); + $this->service->expects(self::never()) + ->method('buildBodyData'); + $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::never()) + ->method('getFrom'); + $this->service->expects(self::never()) + ->method('addSubjectAndHeading'); + $this->service->expects(self::never()) + ->method('addBulletList'); + $this->service->expects(self::never()) + ->method('getAttendeeRsvpOrReqForParticipant'); + $this->config->expects(self::never()) + ->method('getValueString'); + $this->service->expects(self::never()) + ->method('createInvitationToken'); + $this->service->expects(self::never()) + ->method('addResponseButtons'); + $this->service->expects(self::never()) + ->method('addMoreOptionsButton'); + $this->mailer->expects(self::never()) + ->method('send'); + $this->plugin->schedule($message); + $this->assertEquals('1.0', $message->getScheduleStatus()); + } + + public function testAttendeeIsCircle(): 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:' . 'circle+82utEV1Fle8wvxndZLK5TVAPtxj8IIe@middle.earth', ['RSVP' => 'TRUE', 'CN' => 'The Fellowship', 'CUTYPE' => 'GROUP']); + $newVevent->add('ATTENDEE', 'mailto:' . 'boromir@tra.it.or', ['RSVP' => 'TRUE', 'MEMBER' => 'circle+82utEV1Fle8wvxndZLK5TVAPtxj8IIe@middle.earth']); + $message->message = $newVCalendar; + $message->sender = 'mailto:gandalf@wiz.ard'; + $message->senderName = 'Mr. Wizard'; + $message->recipient = 'mailto:' . 'circle+82utEV1Fle8wvxndZLK5TVAPtxj8IIe@middle.earth'; + $attendees = $newVevent->select('ATTENDEE'); + $circle = ''; + foreach ($attendees as $attendee) { + if (strcasecmp($attendee->getValue(), $message->recipient) === 0) { + $circle = $attendee; + } + } + $this->assertNotEmpty($circle, 'Failed to find attendee belonging to the circle'); + $this->service->expects(self::once()) + ->method('getLastOccurrence') + ->willReturn(1496912700); + $this->mailer->expects(self::once()) + ->method('validateMailAddress') + ->with('circle+82utEV1Fle8wvxndZLK5TVAPtxj8IIe@middle.earth') + ->willReturn(true); + $this->eventComparisonService->expects(self::once()) + ->method('findModified') + ->willReturn(['new' => [$newVevent], 'old' => null]); + $this->service->expects(self::once()) + ->method('getCurrentAttendee') + ->with($message) + ->willReturn($circle); + $this->service->expects(self::once()) + ->method('isRoomOrResource') + ->with($circle) + ->willReturn(false); + $this->service->expects(self::once()) + ->method('isCircle') + ->with($circle) + ->willReturn(true); + $this->service->expects(self::never()) ->method('buildBodyData'); $this->user->expects(self::any()) ->method('getUID') @@ -424,6 +510,10 @@ class IMipPluginTest extends TestCase { ->with($atnd) ->willReturn(false); $this->service->expects(self::once()) + ->method('isCircle') + ->with($atnd) + ->willReturn(false); + $this->service->expects(self::once()) ->method('buildBodyData') ->with($newVevent, null) ->willReturn($data); @@ -555,6 +645,10 @@ class IMipPluginTest extends TestCase { ->with($atnd) ->willReturn(false); $this->service->expects(self::once()) + ->method('isCircle') + ->with($atnd) + ->willReturn(false); + $this->service->expects(self::once()) ->method('buildBodyData') ->with($newVevent, null) ->willReturn($data); @@ -661,6 +755,10 @@ class IMipPluginTest extends TestCase { ->with($attendee) ->willReturn(false); $this->service->expects(self::once()) + ->method('isCircle') + ->with($attendee) + ->willReturn(false); + $this->service->expects(self::once()) ->method('buildBodyData') ->with($event, null) ->willReturn($data); @@ -768,6 +866,10 @@ class IMipPluginTest extends TestCase { ->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); @@ -863,6 +965,10 @@ class IMipPluginTest extends TestCase { ->with($atnd) ->willReturn(false); $this->service->expects(self::once()) + ->method('isCircle') + ->with($atnd) + ->willReturn(false); + $this->service->expects(self::once()) ->method('buildBodyData') ->with($newVevent, null) ->willReturn($data); @@ -956,6 +1062,10 @@ class IMipPluginTest extends TestCase { ->with($atnd) ->willReturn(false); $this->service->expects(self::once()) + ->method('isCircle') + ->with($atnd) + ->willReturn(false); + $this->service->expects(self::once()) ->method('buildBodyData') ->with($newVevent, null) ->willReturn($data); |