diff --git a/apps/dav/lib/CalDAV/CalendarImpl.php b/apps/dav/lib/CalDAV/CalendarImpl.php index d4249441b3c47..7b47963240e0a 100644 --- a/apps/dav/lib/CalDAV/CalendarImpl.php +++ b/apps/dav/lib/CalDAV/CalendarImpl.php @@ -24,9 +24,9 @@ use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp; use Sabre\DAV\Exception\Conflict; use Sabre\VObject\Component\VCalendar; -use Sabre\VObject\Component\VEvent; use Sabre\VObject\Component\VTimeZone; use Sabre\VObject\ITip\Message; +use Sabre\VObject\ParseException; use Sabre\VObject\Property; use Sabre\VObject\Reader; @@ -41,6 +41,9 @@ public function __construct( ) { } + private const DAV_PROPERTY_USER_ADDRESS = '{http://sabredav.org/ns}email-address'; + private const DAV_PROPERTY_USER_ADDRESSES = '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'; + /** * @return string defining the technical unique key * @since 13.0.0 @@ -219,58 +222,93 @@ public function createFromStringMinimal(string $name, string $calendarData): voi * @throws CalendarException */ public function handleIMipMessage(string $name, string $calendarData): void { - $server = $this->getInvitationResponseServer(); - - /** @var CustomPrincipalPlugin $plugin */ - $plugin = $server->getServer()->getPlugin('auth'); - // we're working around the previous implementation - // that only allowed the public system principal to be used - // so set the custom principal here - $plugin->setCurrentPrincipal($this->calendar->getPrincipalURI()); - if (empty($this->calendarInfo['uri'])) { - throw new CalendarException('Could not write to calendar as URI parameter is missing'); + try { + /** @var VCalendar $vObject|null */ + $vObject = Reader::read($calendarData); + } catch (ParseException $e) { + throw new CalendarException('iMip message could not be processed because an error occurred while parsing the iMip message', 0, $e); } - // Force calendar change URI - /** @var Schedule\Plugin $schedulingPlugin */ - $schedulingPlugin = $server->getServer()->getPlugin('caldav-schedule'); - // Let sabre handle the rest - $iTipMessage = new Message(); - /** @var VCalendar $vObject */ - $vObject = Reader::read($calendarData); - /** @var VEvent $vEvent */ - $vEvent = $vObject->{'VEVENT'}; - - if ($vObject->{'METHOD'} === null) { - throw new CalendarException('No Method provided for scheduling data. Could not process message'); + // validate the iMip message + if (!isset($vObject->METHOD)) { + throw new CalendarException('iMip message contains no valid method'); } - - if (!isset($vEvent->{'ORGANIZER'}) || !isset($vEvent->{'ATTENDEE'})) { - throw new CalendarException('Could not process scheduling data, neccessary data missing from ICAL'); + if (!isset($vObject->VEVENT)) { + throw new CalendarException('iMip message contains no event'); + } + if (!isset($vObject->VEVENT->UID)) { + throw new CalendarException('iMip message event dose not contain a UID'); } - $organizer = $vEvent->{'ORGANIZER'}->getValue(); - $attendee = $vEvent->{'ATTENDEE'}->getValue(); - - $iTipMessage->method = $vObject->{'METHOD'}->getValue(); - if ($iTipMessage->method === 'REQUEST') { - $iTipMessage->sender = $organizer; - $iTipMessage->recipient = $attendee; - } elseif ($iTipMessage->method === 'REPLY') { - if ($server->isExternalAttendee($vEvent->{'ATTENDEE'}->getValue())) { - $iTipMessage->recipient = $organizer; - } else { - $iTipMessage->recipient = $attendee; + if (!isset($vObject->VEVENT->ORGANIZER)) { + throw new CalendarException('iMip message event dose not contain an organizer'); + } + if (!isset($vObject->VEVENT->ATTENDEE)) { + throw new CalendarException('iMip message event dose not contain an attendee'); + } + if (empty($this->calendarInfo['uri'])) { + throw new CalendarException('Could not write to calendar as URI parameter is missing'); + } + // construct dav server + $server = $this->getInvitationResponseServer(); + /** @var CustomPrincipalPlugin $authPlugin */ + $authPlugin = $server->getServer()->getPlugin('auth'); + // we're working around the previous implementation + // that only allowed the public system principal to be used + // so set the custom principal here + $authPlugin->setCurrentPrincipal($this->calendar->getPrincipalURI()); + // retrieve all users addresses + $userProperties = $server->getServer()->getProperties($this->calendar->getPrincipalURI(), [ self::DAV_PROPERTY_USER_ADDRESS, self::DAV_PROPERTY_USER_ADDRESSES ]); + $userAddress = 'mailto:' . ($userProperties[self::DAV_PROPERTY_USER_ADDRESS] ?? null); + $userAddresses = $userProperties[self::DAV_PROPERTY_USER_ADDRESSES]->getHrefs() ?? []; + $userAddresses = array_map('strtolower', array_map('urldecode', $userAddresses)); + // validate the method, recipient and sender + $imipMethod = strtoupper($vObject->METHOD->getValue()); + if (in_array($imipMethod, ['REPLY', 'REFRESH'], true)) { + // extract sender (REPLY and REFRESH method should only have one attendee) + $sender = strtolower($vObject->VEVENT->ATTENDEE->getValue()); + // extract and verify the recipient + $recipient = strtolower($vObject->VEVENT->ORGANIZER->getValue()); + if (!in_array($recipient, $userAddresses, true)) { + throw new CalendarException('iMip message dose not contain an organizer that matches the user'); + } + // if the recipient address is not the same as the user address this means an alias was used + // the iTip broker uses the users primary email address during processing + if ($userAddress !== $recipient) { + $recipient = $userAddress; + } + } elseif (in_array($imipMethod, ['PUBLISH', 'REQUEST', 'ADD', 'CANCEL'], true)) { + // extract sender + $sender = strtolower($vObject->VEVENT->ORGANIZER->getValue()); + // extract and verify the recipient + foreach ($vObject->VEVENT->ATTENDEE as $attendee) { + $recipient = strtolower($attendee->getValue()); + if (in_array($recipient, $userAddresses, true)) { + break; + } + $recipient = null; + } + if ($recipient === null) { + throw new CalendarException('iMip message dose not contain an attendee that matches the user'); + } + // if the recipient address is not the same as the user address this means an alias was used + // the iTip broker uses the users primary email address during processing + if ($userAddress !== $recipient) { + $recipient = $userAddress; } - $iTipMessage->sender = $attendee; - } elseif ($iTipMessage->method === 'CANCEL') { - $iTipMessage->recipient = $attendee; - $iTipMessage->sender = $organizer; + } else { + throw new CalendarException('iMip message contains a method that is not supported: ' . $imipMethod); } - $iTipMessage->uid = isset($vEvent->{'UID'}) ? $vEvent->{'UID'}->getValue() : ''; - $iTipMessage->component = 'VEVENT'; - $iTipMessage->sequence = isset($vEvent->{'SEQUENCE'}) ? (int)$vEvent->{'SEQUENCE'}->getValue() : 0; - $iTipMessage->message = $vObject; - $server->server->emit('schedule', [$iTipMessage]); + // generate the iTip message + $iTip = new Message(); + $iTip->method = $imipMethod; + $iTip->sender = $sender; + $iTip->recipient = $recipient; + $iTip->component = 'VEVENT'; + $iTip->uid = $vObject->VEVENT->UID->getValue(); + $iTip->sequence = isset($vObject->VEVENT->SEQUENCE) ? (int)$vObject->VEVENT->SEQUENCE->getValue() : 1; + $iTip->message = $vObject; + + $server->server->emit('schedule', [$iTip]); } public function getInvitationResponseServer(): InvitationResponseServer { diff --git a/apps/dav/lib/CalDAV/Schedule/Plugin.php b/apps/dav/lib/CalDAV/Schedule/Plugin.php index a001df8b2a86a..d5ddfa09b248c 100644 --- a/apps/dav/lib/CalDAV/Schedule/Plugin.php +++ b/apps/dav/lib/CalDAV/Schedule/Plugin.php @@ -132,7 +132,7 @@ public function propFind(PropFind $propFind, INode $node) { * @param string $principal * @return array */ - protected function getAddressesForPrincipal($principal) { + public function getAddressesForPrincipal($principal) { $result = parent::getAddressesForPrincipal($principal); if ($result === null) { diff --git a/apps/dav/tests/unit/CalDAV/CalendarImplTest.php b/apps/dav/tests/unit/CalDAV/CalendarImplTest.php index d6a8f3b910e3b..6c966d800c7d5 100644 --- a/apps/dav/tests/unit/CalDAV/CalendarImplTest.php +++ b/apps/dav/tests/unit/CalDAV/CalendarImplTest.php @@ -7,51 +7,62 @@ */ namespace OCA\DAV\Tests\unit\CalDAV; -use Generator; use OCA\DAV\CalDAV\Auth\CustomPrincipalPlugin; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\Calendar; use OCA\DAV\CalDAV\CalendarImpl; use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer; -use OCA\DAV\CalDAV\Schedule\Plugin; use OCA\DAV\Connector\Sabre\Server; use OCP\Calendar\Exceptions\CalendarException; use PHPUnit\Framework\MockObject\MockObject; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Component\VEvent; use Sabre\VObject\ITip\Message; -use Sabre\VObject\Reader; class CalendarImplTest extends \Test\TestCase { - private Calendar&MockObject $calendar; + private CalDavBackend|MockObject $backend; + private Calendar|MockObject $calendar; + private CalendarImpl|MockObject $calendarImpl; private array $calendarInfo; - private CalDavBackend&MockObject $backend; - private CalendarImpl $calendarImpl; + private VCalendar $vCalendar1a; private array $mockExportCollection; protected function setUp(): void { parent::setUp(); + $this->backend = $this->createMock(CalDavBackend::class); $this->calendar = $this->createMock(Calendar::class); $this->calendarInfo = [ - 'id' => 1, + 'id' => 'fancy_id_123', '{DAV:}displayname' => 'user readable name 123', '{http://apple.com/ns/ical/}calendar-color' => '#AABBCC', 'uri' => '/this/is/a/uri', 'principaluri' => 'principal/users/foobar' ]; - $this->backend = $this->createMock(CalDavBackend::class); + $this->calendarImpl = new CalendarImpl($this->calendar, $this->calendarInfo, $this->backend); - $this->calendarImpl = new CalendarImpl( - $this->calendar, - $this->calendarInfo, - $this->backend - ); + // construct calendar with a 1 hour event and same start/end time zones + $this->vCalendar1a = new VCalendar(); + /** @var VEvent $vEvent */ + $vEvent = $this->vCalendar1a->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']); + $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']); + $vEvent->add('SEQUENCE', 1); + $vEvent->add('SUMMARY', 'Test Event'); + $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); + $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); } public function testGetKey(): void { - $this->assertEquals($this->calendarImpl->getKey(), 1); + $this->assertEquals($this->calendarImpl->getKey(), 'fancy_id_123'); } public function testGetDisplayname(): void { @@ -123,99 +134,128 @@ public function testGetPermissionAll(): void { $this->assertEquals(31, $this->calendarImpl->getPermissions()); } - public function testHandleImipMessage(): void { - $message = <<vCalendar1a; - /** @var CustomPrincipalPlugin|MockObject $authPlugin */ - $authPlugin = $this->createMock(CustomPrincipalPlugin::class); - $authPlugin->expects(self::once()) - ->method('setCurrentPrincipal') - ->with($this->calendar->getPrincipalURI()); + $this->expectException(CalendarException::class); + $this->expectExceptionMessage('iMip message contains no valid method'); - /** @var \Sabre\DAVACL\Plugin|MockObject $aclPlugin */ - $aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class); + // Act + $this->calendarImpl->handleIMipMessage('fakeUser', $vObject->serialize()); + } + + public function testHandleImipNoEvent(): void { + // Arrange + $vObject = $this->vCalendar1a; + $vObject->add('METHOD', 'REQUEST'); + $vObject->remove('VEVENT'); - /** @var Plugin|MockObject $schedulingPlugin */ - $schedulingPlugin = $this->createMock(Plugin::class); - $iTipMessage = $this->getITipMessage($message); - $iTipMessage->recipient = 'mailto:lewis@stardew-tent-living.com'; + $this->expectException(CalendarException::class); + $this->expectExceptionMessage('iMip message contains no event'); - $server = $this->createMock(Server::class); - $server->expects($this->any()) - ->method('getPlugin') - ->willReturnMap([ - ['auth', $authPlugin], - ['acl', $aclPlugin], - ['caldav-schedule', $schedulingPlugin] - ]); - $server->expects(self::once()) - ->method('emit'); + // Act + $this->calendarImpl->handleIMipMessage('fakeUser', $vObject->serialize()); + } - $invitationResponseServer = $this->createPartialMock(InvitationResponseServer::class, ['getServer', 'isExternalAttendee']); - $invitationResponseServer->server = $server; - $invitationResponseServer->expects($this->any()) - ->method('getServer') - ->willReturn($server); - $invitationResponseServer->expects(self::once()) - ->method('isExternalAttendee') - ->willReturn(false); + public function testHandleImipNoUid(): void { + // Arrange + $vObject = $this->vCalendar1a; + $vObject->add('METHOD', 'REQUEST'); + $vObject->VEVENT->remove('UID'); - $calendarImpl = $this->getMockBuilder(CalendarImpl::class) - ->setConstructorArgs([$this->calendar, $this->calendarInfo, $this->backend]) - ->onlyMethods(['getInvitationResponseServer']) - ->getMock(); - $calendarImpl->expects($this->once()) - ->method('getInvitationResponseServer') - ->willReturn($invitationResponseServer); + $this->expectException(CalendarException::class); + $this->expectExceptionMessage('iMip message event dose not contain a UID'); + + // Act + $this->calendarImpl->handleIMipMessage('fakeUser', $vObject->serialize()); + } + + public function testHandleImipNoOrganizer(): void { + // Arrange + $vObject = $this->vCalendar1a; + $vObject->add('METHOD', 'REQUEST'); + $vObject->VEVENT->remove('ORGANIZER'); + + $this->expectException(CalendarException::class); + $this->expectExceptionMessage('iMip message event dose not contain an organizer'); + + // Act + $this->calendarImpl->handleIMipMessage('fakeUser', $vObject->serialize()); + } + + public function testHandleImipNoAttendee(): void { + // Arrange + $vObject = $this->vCalendar1a; + $vObject->add('METHOD', 'REQUEST'); + $vObject->VEVENT->remove('ATTENDEE'); - $calendarImpl->handleIMipMessage('filename.ics', $message); + $this->expectException(CalendarException::class); + $this->expectExceptionMessage('iMip message event dose not contain an attendee'); + + // Act + $this->calendarImpl->handleIMipMessage('fakeUser', $vObject->serialize()); } - public function testHandleImipMessageNoCalendarUri(): void { + public function testHandleImipRequest(): void { + $userAddressSet = new class([ 'mailto:attendee1@testing.com', '/remote.php/dav/principals/users/attendee1/', ]) { + public function __construct( + private array $hrefs, + ) { + } + public function getHrefs(): array { + return $this->hrefs; + } + }; + + $vObject = $this->vCalendar1a; + $vObject->add('METHOD', 'REQUEST'); + + $iTip = new Message(); + $iTip->method = 'REQUEST'; + $iTip->sender = $vObject->VEVENT->ORGANIZER->getValue(); + $iTip->recipient = $vObject->VEVENT->ATTENDEE->getValue(); + $iTip->component = 'VEVENT'; + $iTip->uid = $vObject->VEVENT->UID->getValue(); + $iTip->sequence = (int)$vObject->VEVENT->SEQUENCE->getValue() ?? 0; + $iTip->message = $vObject; + /** @var CustomPrincipalPlugin|MockObject $authPlugin */ $authPlugin = $this->createMock(CustomPrincipalPlugin::class); $authPlugin->expects(self::once()) ->method('setCurrentPrincipal') ->with($this->calendar->getPrincipalURI()); - unset($this->calendarInfo['uri']); - - /** @var Plugin|MockObject $schedulingPlugin */ - $schedulingPlugin = $this->createMock(Plugin::class); - - /** @var \Sabre\DAVACL\Plugin|MockObject $schedulingPlugin */ + /** @var \Sabre\DAVACL\Plugin|MockObject $aclPlugin */ $aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class); - $server - = $this->createMock(Server::class); + $server = $this->createMock(Server::class); $server->expects($this->any()) ->method('getPlugin') ->willReturnMap([ ['auth', $authPlugin], ['acl', $aclPlugin], - ['caldav-schedule', $schedulingPlugin] ]); - $server->expects(self::never()) - ->method('emit'); - $invitationResponseServer = $this->createPartialMock(InvitationResponseServer::class, ['getServer']); + $server->expects(self::once()) + ->method('getProperties') + ->with( + $this->calendar->getPrincipalURI(), + [ + '{http://sabredav.org/ns}email-address', + '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' + ] + ) + ->willReturn([ + '{http://sabredav.org/ns}email-address' => 'attendee1@testing.com', + '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => $userAddressSet, + ]); + $server->expects(self::once()) + ->method('emit'); + $invitationResponseServer = $this->createMock(InvitationResponseServer::class, ['getServer']); $invitationResponseServer->server = $server; $invitationResponseServer->expects($this->any()) ->method('getServer') ->willReturn($server); - $calendarImpl = $this->getMockBuilder(CalendarImpl::class) ->setConstructorArgs([$this->calendar, $this->calendarInfo, $this->backend]) ->onlyMethods(['getInvitationResponseServer']) @@ -224,85 +264,7 @@ public function testHandleImipMessageNoCalendarUri(): void { ->method('getInvitationResponseServer') ->willReturn($invitationResponseServer); - $message = <<expectException(CalendarException::class); - $calendarImpl->handleIMipMessage('filename.ics', $message); - } - - private function getITipMessage($calendarData): Message { - $iTipMessage = new Message(); - /** @var VCalendar $vObject */ - $vObject = Reader::read($calendarData); - /** @var VEvent $vEvent */ - $vEvent = $vObject->{'VEVENT'}; - $orgaizer = $vEvent->{'ORGANIZER'}->getValue(); - $attendee = $vEvent->{'ATTENDEE'}->getValue(); - - $iTipMessage->method = $vObject->{'METHOD'}->getValue(); - $iTipMessage->recipient = $orgaizer; - $iTipMessage->sender = $attendee; - $iTipMessage->uid = isset($vEvent->{'UID'}) ? $vEvent->{'UID'}->getValue() : ''; - $iTipMessage->component = 'VEVENT'; - $iTipMessage->sequence = isset($vEvent->{'SEQUENCE'}) ? (int)$vEvent->{'SEQUENCE'}->getValue() : 0; - $iTipMessage->message = $vObject; - return $iTipMessage; - } - - protected function mockExportGenerator(): Generator { - foreach ($this->mockExportCollection as $entry) { - yield $entry; - } - } - - public function testExport(): void { - // Arrange - // construct calendar with a 1 hour event and same start/end time zones - $vCalendar = new VCalendar(); - /** @var VEvent $vEvent */ - $vEvent = $vCalendar->add('VEVENT', []); - $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); - $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']); - $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']); - $vEvent->add('SUMMARY', 'Test Recurrence Event'); - $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); - $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ - 'CN' => 'Attendee One', - 'CUTYPE' => 'INDIVIDUAL', - 'PARTSTAT' => 'NEEDS-ACTION', - 'ROLE' => 'REQ-PARTICIPANT', - 'RSVP' => 'TRUE' - ]); - // construct data store return - $this->mockExportCollection[] = [ - 'id' => 1, - 'calendardata' => $vCalendar->serialize() - ]; - $this->backend->expects($this->once()) - ->method('exportCalendar') - ->with(1, $this->backend::CALENDAR_TYPE_CALENDAR, null) - ->willReturn($this->mockExportGenerator()); - - // Act - foreach ($this->calendarImpl->export(null) as $entry) { - $exported[] = $entry; - } - - // Assert - $this->assertCount(1, $exported, 'Invalid exported items count'); + $calendarImpl->handleIMipMessage('fakeUser', $vObject->serialize()); } } diff --git a/lib/private/Calendar/Manager.php b/lib/private/Calendar/Manager.php index 7da1379809d0a..13e310d8221d8 100644 --- a/lib/private/Calendar/Manager.php +++ b/lib/private/Calendar/Manager.php @@ -16,7 +16,6 @@ use OCP\Calendar\Exceptions\CalendarException; use OCP\Calendar\ICalendar; use OCP\Calendar\ICalendarEventBuilder; -use OCP\Calendar\ICalendarIsShared; use OCP\Calendar\ICalendarIsWritable; use OCP\Calendar\ICalendarProvider; use OCP\Calendar\ICalendarQuery; @@ -221,17 +220,18 @@ public function newQuery(string $principalUri): ICalendarQuery { } /** - * @since 31.0.0 + * @since 32.0.0 + * * @throws \OCP\DB\Exception */ - public function handleIMipRequest( - string $principalUri, - string $sender, - string $recipient, - string $calendarData, + public function handleIMip( + string $userId, + string $message, ): bool { - $userCalendars = $this->getCalendarsForPrincipal($principalUri); + $userUri = 'principals/users/' . $userId; + + $userCalendars = $this->getCalendarsForPrincipal($userUri); if (empty($userCalendars)) { $this->logger->warning('iMip message could not be processed because user has no calendars'); return false; @@ -239,188 +239,100 @@ public function handleIMipRequest( try { /** @var VCalendar $vObject|null */ - $calendarObject = Reader::read($calendarData); + $vObject = Reader::read($message); } catch (ParseException $e) { $this->logger->error('iMip message could not be processed because an error occurred while parsing the iMip message', ['exception' => $e]); return false; } - if (!isset($calendarObject->METHOD) || $calendarObject->METHOD->getValue() !== 'REQUEST') { - $this->logger->warning('iMip message contains an incorrect or invalid method'); - return false; - } - - if (!isset($calendarObject->VEVENT)) { - $this->logger->warning('iMip message contains no event'); + if (!isset($vObject->VEVENT)) { + $this->logger->warning('iMip message does not contain any event(s)'); return false; } + /** @var VEvent $vEvent */ + $vEvent = $vObject->VEVENT; - /** @var VEvent|null $vEvent */ - $eventObject = $calendarObject->VEVENT; - - if (!isset($eventObject->UID)) { + if (!isset($vEvent->UID)) { $this->logger->warning('iMip message event dose not contains a UID'); return false; } - if (!isset($eventObject->ORGANIZER)) { + if (!isset($vEvent->ORGANIZER)) { $this->logger->warning('iMip message event dose not contains an organizer'); return false; } - if (!isset($eventObject->ATTENDEE)) { + if (!isset($vEvent->ATTENDEE)) { $this->logger->warning('iMip message event dose not contains any attendees'); return false; } - foreach ($eventObject->ATTENDEE as $entry) { - $address = trim(str_replace('mailto:', '', $entry->getValue())); - if ($address === $recipient) { - $attendee = $address; - break; - } - } - if (!isset($attendee)) { - $this->logger->warning('iMip message event does not contain a attendee that matches the recipient'); - return false; - } - foreach ($userCalendars as $calendar) { - - if (!$calendar instanceof ICalendarIsWritable && !$calendar instanceof ICalendarIsShared) { + if (!$calendar instanceof ICalendarIsWritable) { continue; } - - if ($calendar->isDeleted() || !$calendar->isWritable() || $calendar->isShared()) { + if ($calendar->isDeleted() || !$calendar->isWritable()) { continue; } - - if (!empty($calendar->search($recipient, ['ATTENDEE'], ['uid' => $eventObject->UID->getValue()]))) { + if (!empty($calendar->search('', [], ['uid' => $vEvent->UID->getValue()]))) { try { if ($calendar instanceof IHandleImipMessage) { - $calendar->handleIMipMessage('', $calendarData); + $calendar->handleIMipMessage($userId, $vObject->serialize()); } return true; } catch (CalendarException $e) { - $this->logger->error('An error occurred while processing the iMip message event', ['exception' => $e]); + $this->logger->error('iMip message could not be processed because an error occurred', ['exception' => $e]); return false; } } } - $this->logger->warning('iMip message event could not be processed because no corresponding event was found in any calendar'); + $this->logger->warning('iMip message could not be processed because no corresponding event was found in any calendar'); + return false; } /** + * @since 31.0.0 + * * @throws \OCP\DB\Exception */ - public function handleIMipReply( + public function handleIMipRequest( string $principalUri, string $sender, string $recipient, string $calendarData, ): bool { - - $calendars = $this->getCalendarsForPrincipal($principalUri); - if (empty($calendars)) { - $this->logger->warning('iMip message could not be processed because user has no calendars'); - return false; - } - - try { - /** @var VCalendar $vObject|null */ - $vObject = Reader::read($calendarData); - } catch (ParseException $e) { - $this->logger->error('iMip message could not be processed because an error occurred while parsing the iMip message', ['exception' => $e]); - return false; - } - - if ($vObject === null) { - $this->logger->warning('iMip message contains an invalid calendar object'); - return false; - } - - if (!isset($vObject->METHOD) || $vObject->METHOD->getValue() !== 'REPLY') { - $this->logger->warning('iMip message contains an incorrect or invalid method'); - return false; - } - - if (!isset($vObject->VEVENT)) { - $this->logger->warning('iMip message contains no event'); - return false; - } - - /** @var VEvent|null $vEvent */ - $vEvent = $vObject->VEVENT; - - if (!isset($vEvent->UID)) { - $this->logger->warning('iMip message event dose not contains a UID'); - return false; - } - - if (!isset($vEvent->ORGANIZER)) { - $this->logger->warning('iMip message event dose not contains an organizer'); - return false; - } - - if (!isset($vEvent->ATTENDEE)) { - $this->logger->warning('iMip message event dose not contains any attendees'); - return false; - } - - // check if mail recipient and organizer are one and the same - $organizer = substr($vEvent->{'ORGANIZER'}->getValue(), 7); - - if (strcasecmp($recipient, $organizer) !== 0) { - $this->logger->warning('iMip message event could not be processed because recipient and ORGANIZER must be identical'); - return false; - } - - //check if the event is in the future - /** @var DateTime $eventTime */ - $eventTime = $vEvent->{'DTSTART'}; - if ($eventTime->getDateTime()->getTimeStamp() < $this->timeFactory->getTime()) { // this might cause issues with recurrences - $this->logger->warning('iMip message event could not be processed because the event is in the past'); - return false; - } - - $found = null; - // if the attendee has been found in at least one calendar event with the UID of the iMIP event - // we process it. - // Benefit: no attendee lost - // Drawback: attendees that have been deleted will still be able to update their partstat - foreach ($calendars as $calendar) { - // We should not search in writable calendars - if ($calendar instanceof IHandleImipMessage) { - $o = $calendar->search($sender, ['ATTENDEE'], ['uid' => $vEvent->{'UID'}->getValue()]); - if (!empty($o)) { - $found = $calendar; - $name = $o[0]['uri']; - break; - } - } - } - - if (empty($found)) { - $this->logger->warning('iMip message event could not be processed because no corresponding event was found in any calendar', [ - 'principalUri' => $principalUri, - 'eventUid' => $vEvent->{'UID'}->getValue(), - ]); + if (empty($principalUri) || !str_starts_with($principalUri, 'principals/users/')) { + $this->logger->error('Invalid principal URI provided for iMip request'); return false; } + $userId = substr($principalUri, 17); + return $this->handleIMip($userId, $calendarData); + } - try { - $found->handleIMipMessage($name, $calendarData); // sabre will handle the scheduling behind the scenes - } catch (CalendarException $e) { - $this->logger->error('An error occurred while processing the iMip message event', ['exception' => $e]); + /** + * @since 25.0.0 + * + * @throws \OCP\DB\Exception + */ + public function handleIMipReply( + string $principalUri, + string $sender, + string $recipient, + string $calendarData, + ): bool { + if (empty($principalUri) || !str_starts_with($principalUri, 'principals/users/')) { + $this->logger->error('Invalid principal URI provided for iMip reply'); return false; } - return true; + $userId = substr($principalUri, 17); + return $this->handleIMip($userId, $calendarData); } /** * @since 25.0.0 + * * @throws \OCP\DB\Exception */ public function handleIMipCancel( @@ -430,111 +342,12 @@ public function handleIMipCancel( string $recipient, string $calendarData, ): bool { - - $calendars = $this->getCalendarsForPrincipal($principalUri); - if (empty($calendars)) { - $this->logger->warning('iMip message could not be processed because user has no calendars'); - return false; - } - - try { - /** @var VCalendar $vObject|null */ - $vObject = Reader::read($calendarData); - } catch (ParseException $e) { - $this->logger->error('iMip message could not be processed because an error occurred while parsing the iMip message', ['exception' => $e]); - return false; - } - - if ($vObject === null) { - $this->logger->warning('iMip message contains an invalid calendar object'); - return false; - } - - if (!isset($vObject->METHOD) || $vObject->METHOD->getValue() !== 'CANCEL') { - $this->logger->warning('iMip message contains an incorrect or invalid method'); - return false; - } - - if (!isset($vObject->VEVENT)) { - $this->logger->warning('iMip message contains no event'); - return false; - } - - /** @var VEvent|null $vEvent */ - $vEvent = $vObject->{'VEVENT'}; - - if (!isset($vEvent->UID)) { - $this->logger->warning('iMip message event dose not contains a UID'); - return false; - } - - if (!isset($vEvent->ORGANIZER)) { - $this->logger->warning('iMip message event dose not contains an organizer'); - return false; - } - - if (!isset($vEvent->ATTENDEE)) { - $this->logger->warning('iMip message event dose not contains any attendees'); - return false; - } - - $attendee = substr($vEvent->{'ATTENDEE'}->getValue(), 7); - if (strcasecmp($recipient, $attendee) !== 0) { - $this->logger->warning('iMip message event could not be processed because recipient must be an ATTENDEE of this event'); - return false; - } - - // Thirdly, we need to compare the email address the CANCEL is coming from (in Mail) - // or the Reply- To Address submitted with the CANCEL email - // to the email address in the ORGANIZER. - // We don't want to accept a CANCEL request from just anyone - $organizer = substr($vEvent->{'ORGANIZER'}->getValue(), 7); - $isNotOrganizer = ($replyTo !== null) ? (strcasecmp($sender, $organizer) !== 0 && strcasecmp($replyTo, $organizer) !== 0) : (strcasecmp($sender, $organizer) !== 0); - if ($isNotOrganizer) { - $this->logger->warning('iMip message event could not be processed because sender must be the ORGANIZER of this event'); - return false; - } - - //check if the event is in the future - /** @var DateTime $eventTime */ - $eventTime = $vEvent->{'DTSTART'}; - if ($eventTime->getDateTime()->getTimeStamp() < $this->timeFactory->getTime()) { // this might cause issues with recurrences - $this->logger->warning('iMip message event could not be processed because the event is in the past'); - return false; - } - - $found = null; - // if the attendee has been found in at least one calendar event with the UID of the iMIP event - // we process it. - // Benefit: no attendee lost - // Drawback: attendees that have been deleted will still be able to update their partstat - foreach ($calendars as $calendar) { - // We should not search in writable calendars - if ($calendar instanceof IHandleImipMessage) { - $o = $calendar->search($recipient, ['ATTENDEE'], ['uid' => $vEvent->{'UID'}->getValue()]); - if (!empty($o)) { - $found = $calendar; - $name = $o[0]['uri']; - break; - } - } - } - - if (empty($found)) { - $this->logger->warning('iMip message event could not be processed because no corresponding event was found in any calendar', [ - 'principalUri' => $principalUri, - 'eventUid' => $vEvent->{'UID'}->getValue(), - ]); - return false; - } - - try { - $found->handleIMipMessage($name, $calendarData); // sabre will handle the scheduling behind the scenes - return true; - } catch (CalendarException $e) { - $this->logger->error('An error occurred while processing the iMip message event', ['exception' => $e]); + if (empty($principalUri) || !str_starts_with($principalUri, 'principals/users/')) { + $this->logger->error('Invalid principal URI provided for iMip cancel'); return false; } + $userId = substr($principalUri, 17); + return $this->handleIMip($userId, $calendarData); } public function createEventBuilder(): ICalendarEventBuilder { diff --git a/lib/public/Calendar/IManager.php b/lib/public/Calendar/IManager.php index 124dc65f5f6af..ea0a759a23926 100644 --- a/lib/public/Calendar/IManager.php +++ b/lib/public/Calendar/IManager.php @@ -140,6 +140,15 @@ public function searchForPrincipal(ICalendarQuery $query): array; */ public function newQuery(string $principalUri) : ICalendarQuery; + /** + * Handles a iMip message + * + * @since 32.0.0 + * + * @throws \OCP\DB\Exception + */ + public function handleIMip(string $userId, string $message): bool; + /** * Handle a iMip REQUEST message * diff --git a/tests/lib/Calendar/ManagerTest.php b/tests/lib/Calendar/ManagerTest.php index eeaca0dfeb630..bd0bdbde9b2e3 100644 --- a/tests/lib/Calendar/ManagerTest.php +++ b/tests/lib/Calendar/ManagerTest.php @@ -16,6 +16,8 @@ use OCA\DAV\ServerFactory; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Calendar\ICalendar; +use OCP\Calendar\ICalendarExport; +use OCP\Calendar\ICalendarIsEnabled; use OCP\Calendar\ICalendarIsShared; use OCP\Calendar\ICalendarIsWritable; use OCP\Calendar\ICreateFromString; @@ -34,7 +36,8 @@ /* * This allows us to create Mock object supporting both interfaces */ -interface ITestCalendar extends ICreateFromString, IHandleImipMessage, ICalendarIsShared, ICalendarIsWritable { +interface ITestCalendar extends ICreateFromString, IHandleImipMessage, ICalendarIsEnabled, ICalendarIsWritable, ICalendarIsShared, ICalendarExport { + } class ManagerTest extends TestCase { @@ -313,7 +316,7 @@ public function testIfEnabledIfSo(): void { $this->assertTrue($isEnabled); } - public function testHandleImipRequestWithNoCalendars(): void { + public function testHandleImipWithNoCalendars(): void { // construct calendar manager returns /** @var Manager&MockObject $manager */ $manager = $this->getMockBuilder(Manager::class) @@ -335,120 +338,16 @@ public function testHandleImipRequestWithNoCalendars(): void { $this->logger->expects(self::once())->method('warning') ->with('iMip message could not be processed because user has no calendars'); // construct parameters - $principalUri = 'principals/user/attendee1'; - $sender = 'organizer@testing.com'; - $recipient = 'attendee1@testing.com'; + $userId = 'attendee1'; $calendar = $this->vCalendar1a; $calendar->add('METHOD', 'REQUEST'); - // Act - $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipRequestWithInvalidData(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('error') - ->with('iMip message could not be processed because an error occurred while parsing the iMip message'); - // construct parameters - $principalUri = 'principals/user/attendee1'; - $sender = 'organizer@testing.com'; - $recipient = 'attendee1@testing.com'; - // Act - $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, 'Invalid data'); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipRequestWithNoMethod(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message contains an incorrect or invalid method'); - // construct parameters - $principalUri = 'principals/user/attendee1'; - $sender = 'organizer@testing.com'; - $recipient = 'attendee1@testing.com'; - $calendar = $this->vCalendar1a; - // Act - $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipRequestWithInvalidMethod(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message contains an incorrect or invalid method'); - // construct parameters - $principalUri = 'principals/user/attendee1'; - $sender = 'organizer@testing.com'; - $recipient = 'attendee1@testing.com'; - $calendar = $this->vCalendar1a; - $calendar->add('METHOD', 'CANCEL'); - // Act - $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize()); + // test method + $result = $manager->handleIMip($userId, $calendar->serialize()); // Assert $this->assertFalse($result); } - public function testHandleImipRequestWithNoEvent(): void { + public function testHandleImipWithNoEvent(): void { // construct mock user calendar $userCalendar = $this->createMock(ITestCalendar::class); // construct mock calendar manager and returns @@ -470,21 +369,19 @@ public function testHandleImipRequestWithNoEvent(): void { ->willReturn([$userCalendar]); // construct logger returns $this->logger->expects(self::once())->method('warning') - ->with('iMip message contains no event'); + ->with('iMip message does not contain any event(s)'); // construct parameters - $principalUri = 'principals/user/attendee1'; - $sender = 'organizer@testing.com'; - $recipient = 'attendee1@testing.com'; + $userId = 'attendee1'; $calendar = $this->vCalendar1a; $calendar->add('METHOD', 'REQUEST'); $calendar->remove('VEVENT'); // Act - $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize()); + $result = $manager->handleIMip($userId, $calendar->serialize()); // Assert $this->assertFalse($result); } - public function testHandleImipRequestWithNoUid(): void { + public function testHandleImipWithNoUid(): void { // construct mock user calendar $userCalendar = $this->createMock(ITestCalendar::class); // construct mock calendar manager and returns @@ -508,126 +405,17 @@ public function testHandleImipRequestWithNoUid(): void { $this->logger->expects(self::once())->method('warning') ->with('iMip message event dose not contains a UID'); // construct parameters - $principalUri = 'principals/user/attendee1'; - $sender = 'organizer@testing.com'; - $recipient = 'attendee1@testing.com'; + $userId = 'attendee1'; $calendar = $this->vCalendar1a; $calendar->add('METHOD', 'REQUEST'); $calendar->VEVENT->remove('UID'); - // Act - $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipRequestWithNoOrganizer(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message event dose not contains an organizer'); - // construct parameters - $principalUri = 'principals/user/attendee1'; - $sender = 'organizer@testing.com'; - $recipient = 'attendee1@testing.com'; - $calendar = $this->vCalendar1a; - $calendar->add('METHOD', 'REQUEST'); - $calendar->VEVENT->remove('ORGANIZER'); - // Act - $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipRequestWithNoAttendee(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message event dose not contains any attendees'); - // construct parameters - $principalUri = 'principals/user/attendee1'; - $sender = 'organizer@testing.com'; - $recipient = 'attendee1@testing.com'; - $calendar = $this->vCalendar1a; - $calendar->add('METHOD', 'REQUEST'); - $calendar->VEVENT->remove('ATTENDEE'); - // Act - $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipRequestWithInvalidAttendee(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message event does not contain a attendee that matches the recipient'); - // construct parameters - $principalUri = 'principals/user/attendee1'; - $sender = 'organizer@testing.com'; - $recipient = 'attendee2@testing.com'; - $calendar = $this->vCalendar1a; - $calendar->add('METHOD', 'REQUEST'); - // Act - $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize()); + // test method + $result = $manager->handleIMip($userId, $calendar->serialize()); // Assert $this->assertFalse($result); } - public function testHandleImipRequestWithNoMatch(): void { + public function testHandleImipWithNoMatch(): void { // construct mock user calendar $userCalendar = $this->createMock(ITestCalendar::class); $userCalendar->expects(self::once()) @@ -636,9 +424,6 @@ public function testHandleImipRequestWithNoMatch(): void { $userCalendar->expects(self::once()) ->method('isWritable') ->willReturn(true); - $userCalendar->expects(self::once()) - ->method('isShared') - ->willReturn(false); $userCalendar->expects(self::once()) ->method('search') ->willReturn([]); @@ -661,20 +446,18 @@ public function testHandleImipRequestWithNoMatch(): void { ->willReturn([$userCalendar]); // construct logger returns $this->logger->expects(self::once())->method('warning') - ->with('iMip message event could not be processed because no corresponding event was found in any calendar'); + ->with('iMip message could not be processed because no corresponding event was found in any calendar'); // construct parameters - $principalUri = 'principals/user/attendee1'; - $sender = 'organizer@testing.com'; - $recipient = 'attendee1@testing.com'; + $userId = 'attendee1'; $calendar = $this->vCalendar1a; $calendar->add('METHOD', 'REQUEST'); - // Act - $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize()); + // test method + $result = $manager->handleIMip($userId, $calendar->serialize()); // Assert $this->assertFalse($result); } - public function testHandleImipRequest(): void { + public function testHandleImip(): void { // construct mock user calendar $userCalendar = $this->createMock(ITestCalendar::class); $userCalendar->expects(self::once()) @@ -683,9 +466,6 @@ public function testHandleImipRequest(): void { $userCalendar->expects(self::once()) ->method('isWritable') ->willReturn(true); - $userCalendar->expects(self::once()) - ->method('isShared') - ->willReturn(false); $userCalendar->expects(self::once()) ->method('search') ->willReturn([['uri' => 'principals/user/attendee1/personal']]); @@ -707,58 +487,36 @@ public function testHandleImipRequest(): void { ->method('getCalendarsForPrincipal') ->willReturn([$userCalendar]); // construct parameters - $principalUri = 'principals/user/attendee1'; - $sender = 'organizer@testing.com'; - $recipient = 'attendee1@testing.com'; + $userId = 'attendee1'; $calendar = $this->vCalendar1a; $calendar->add('METHOD', 'REQUEST'); // construct user calendar returns $userCalendar->expects(self::once()) - ->method('handleIMipMessage') - ->with('', $calendar->serialize()); - // Act - $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendar->serialize()); - // Assert - $this->assertTrue($result); + ->method('handleIMipMessage'); + // test method + $result = $manager->handleIMip($userId, $calendar->serialize()); } - public function testHandleImipReplyWithNoCalendars(): void { - // construct calendar manager returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message could not be processed because user has no calendars'); - // construct parameters - $principalUri = 'principals/user/linus'; - $sender = 'pierre@general-store.com'; - $recipient = 'linus@stardew-tent-living.com'; - $calendar = $this->vCalendar2a; - $calendar->add('METHOD', 'REPLY'); - // Act - $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize()); - // Assert + public function testhandleIMipRequestWithInvalidPrincipal() { + $invalidPrincipal = 'invalid-principal-uri'; + $sender = 'sender@example.com'; + $recipient = 'recipient@example.com'; + $calendarData = $this->vCalendar1a->serialize(); + + $this->logger->expects(self::once()) + ->method('error') + ->with('Invalid principal URI provided for iMip request'); + + $result = $this->manager->handleIMipRequest($invalidPrincipal, $sender, $recipient, $calendarData); $this->assertFalse($result); } - public function testHandleImipReplyWithInvalidData(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns + public function testhandleIMipRequest() { + $principalUri = 'principals/users/attendee1'; + $sender = 'sender@example.com'; + $recipient = 'recipient@example.com'; + $calendarData = $this->vCalendar1a->serialize(); + /** @var Manager&MockObject $manager */ $manager = $this->getMockBuilder(Manager::class) ->setConstructorArgs([ @@ -770,28 +528,37 @@ public function testHandleImipReplyWithInvalidData(): void { $this->userManager, $this->serverFactory, ]) - ->onlyMethods(['getCalendarsForPrincipal']) + ->onlyMethods(['handleIMip']) ->getMock(); $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('error') - ->with('iMip message could not be processed because an error occurred while parsing the iMip message'); - // construct parameters - $principalUri = 'principals/user/attendee1'; - $sender = 'organizer@testing.com'; - $recipient = 'attendee1@testing.com'; - // Act - $result = $manager->handleIMipReply($principalUri, $sender, $recipient, 'Invalid data'); - // Assert + ->method('handleIMip') + ->with('attendee1', $calendarData) + ->willReturn(true); + + $result = $manager->handleIMipRequest($principalUri, $sender, $recipient, $calendarData); + $this->assertTrue($result); + } + + public function testhandleIMipReplyWithInvalidPrincipal() { + $invalidPrincipal = 'invalid-principal-uri'; + $sender = 'sender@example.com'; + $recipient = 'recipient@example.com'; + $calendarData = $this->vCalendar2a->serialize(); + + $this->logger->expects(self::once()) + ->method('error') + ->with('Invalid principal URI provided for iMip reply'); + + $result = $this->manager->handleIMipReply($invalidPrincipal, $sender, $recipient, $calendarData); $this->assertFalse($result); } - public function testHandleImipReplyWithNoMethod(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns + public function testhandleIMipReply() { + $principalUri = 'principals/users/attendee2'; + $sender = 'sender@example.com'; + $recipient = 'recipient@example.com'; + $calendarData = $this->vCalendar2a->serialize(); + /** @var Manager&MockObject $manager */ $manager = $this->getMockBuilder(Manager::class) ->setConstructorArgs([ @@ -803,29 +570,39 @@ public function testHandleImipReplyWithNoMethod(): void { $this->userManager, $this->serverFactory, ]) - ->onlyMethods(['getCalendarsForPrincipal']) + ->onlyMethods(['handleIMip']) ->getMock(); $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message contains an incorrect or invalid method'); - // construct parameters - $principalUri = 'principals/user/linus'; - $sender = 'pierre@general-store.com'; - $recipient = 'linus@stardew-tent-living.com'; - $calendar = $this->vCalendar2a; - // Act - $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize()); - // Assert + ->method('handleIMip') + ->with('attendee2', $calendarData) + ->willReturn(true); + + $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData); + $this->assertTrue($result); + } + + public function testhandleIMipCancelWithInvalidPrincipal() { + $invalidPrincipal = 'invalid-principal-uri'; + $sender = 'sender@example.com'; + $replyTo = null; + $recipient = 'recipient@example.com'; + $calendarData = $this->vCalendar3a->serialize(); + + $this->logger->expects(self::once()) + ->method('error') + ->with('Invalid principal URI provided for iMip cancel'); + + $result = $this->manager->handleIMipCancel($invalidPrincipal, $sender, $replyTo, $recipient, $calendarData); $this->assertFalse($result); } - public function testHandleImipReplyWithInvalidMethod(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns + public function testhandleIMipCancel() { + $principalUri = 'principals/users/attendee3'; + $sender = 'sender@example.com'; + $replyTo = null; + $recipient = 'recipient@example.com'; + $calendarData = $this->vCalendar3a->serialize(); + /** @var Manager&MockObject $manager */ $manager = $this->getMockBuilder(Manager::class) ->setConstructorArgs([ @@ -837,783 +614,14 @@ public function testHandleImipReplyWithInvalidMethod(): void { $this->userManager, $this->serverFactory, ]) - ->onlyMethods(['getCalendarsForPrincipal']) + ->onlyMethods(['handleIMip']) ->getMock(); $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message contains an incorrect or invalid method'); - // construct parameters - $principalUri = 'principals/user/linus'; - $sender = 'pierre@general-store.com'; - $recipient = 'linus@stardew-tent-living.com'; - $calendar = $this->vCalendar2a; - $calendar->add('METHOD', 'UNKNOWN'); - // Act - $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipReplyWithNoEvent(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message contains no event'); - // construct parameters - $principalUri = 'principals/user/linus'; - $sender = 'pierre@general-store.com'; - $recipient = 'linus@stardew-tent-living.com'; - $calendar = $this->vCalendar2a; - $calendar->add('METHOD', 'REPLY'); - $calendar->remove('VEVENT'); - // Act - $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipReplyWithNoUid(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message event dose not contains a UID'); - // construct parameters - $principalUri = 'principals/user/linus'; - $sender = 'pierre@general-store.com'; - $recipient = 'linus@stardew-tent-living.com'; - $calendar = $this->vCalendar2a; - $calendar->add('METHOD', 'REPLY'); - $calendar->VEVENT->remove('UID'); - // Act - $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipReplyWithNoOrganizer(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message event dose not contains an organizer'); - // construct parameters - $principalUri = 'principals/user/linus'; - $sender = 'pierre@general-store.com'; - $recipient = 'linus@stardew-tent-living.com'; - $calendar = $this->vCalendar2a; - $calendar->add('METHOD', 'REPLY'); - $calendar->VEVENT->remove('ORGANIZER'); - // Act - $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipReplyWithNoAttendee(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message event dose not contains any attendees'); - // construct parameters - $principalUri = 'principals/user/linus'; - $sender = 'pierre@general-store.com'; - $recipient = 'linus@stardew-tent-living.com'; - $calendar = $this->vCalendar2a; - $calendar->add('METHOD', 'REPLY'); - $calendar->VEVENT->remove('ATTENDEE'); - // Act - $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipReplyDateInThePast(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger and time returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message event could not be processed because the event is in the past'); - $this->time->expects(self::once()) - ->method('getTime') - ->willReturn(time()); - // construct parameters - $principalUri = 'principals/user/linus'; - $sender = 'pierre@general-store.com'; - $recipient = 'linus@stardew-tent-living.com'; - $calendarData = clone $this->vCalendar2a; - $calendarData->add('METHOD', 'REPLY'); - $calendarData->VEVENT->DTSTART = new \DateTime('2013-04-07'); // set to in the past - // Act - $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipReplyEventNotFound(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - $userCalendar->expects(self::once()) - ->method('search') - ->willReturn([]); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct time returns - $this->time->expects(self::once()) - ->method('getTime') - ->willReturn(1628374233); - // construct parameters - $principalUri = 'principals/user/linus'; - $sender = 'pierre@general-store.com'; - $recipient = 'linus@stardew-tent-living.com'; - $calendarData = clone $this->vCalendar2a; - $calendarData->add('METHOD', 'REPLY'); - // construct logger return - $this->logger->expects(self::once())->method('warning') - ->with('iMip message event could not be processed because no corresponding event was found in any calendar', ['principalUri' => $principalUri, 'eventUid' => $calendarData->VEVENT->UID->getValue()]); - // Act - $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipReply(): void { - /** @var Manager | \PHPUnit\Framework\MockObject\MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods([ - 'getCalendarsForPrincipal' - ]) - ->getMock(); - $calendar = $this->createMock(ITestCalendar::class); - $principalUri = 'principals/user/linus'; - $sender = 'pierre@general-store.com'; - $recipient = 'linus@stardew-tent-living.com'; - $calendarData = clone $this->vCalendar2a; - $calendarData->add('METHOD', 'REPLY'); - - $this->time->expects(self::once()) - ->method('getTime') - ->willReturn(1628374233); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$calendar]); - $calendar->expects(self::once()) - ->method('search') - ->willReturn([['uri' => 'testname.ics']]); - $calendar->expects(self::once()) - ->method('handleIMipMessage') - ->with('testname.ics', $calendarData->serialize()); - // Act - $result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData->serialize()); - // Assert - $this->assertTrue($result); - } - - public function testHandleImipCancelWithNoCalendars(): void { - // construct calendar manager returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message could not be processed because user has no calendars'); - // construct parameters - $principalUri = 'principals/user/pierre'; - $sender = 'linus@stardew-tent-living.com'; - $recipient = 'pierre@general-store.com'; - $replyTo = null; - $calendar = $this->vCalendar3a; - $calendar->add('METHOD', 'CANCEL'); - // Act - $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipCancelWithInvalidData(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('error') - ->with('iMip message could not be processed because an error occurred while parsing the iMip message'); - // construct parameters - $principalUri = 'principals/user/attendee1'; - $sender = 'organizer@testing.com'; - $recipient = 'attendee1@testing.com'; - $replyTo = null; - // Act - $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, 'Invalid data'); - // Assert - $this->assertFalse($result); - } - - - public function testHandleImipCancelWithNoMethod(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message contains an incorrect or invalid method'); - // construct parameters - $principalUri = 'principals/user/pierre'; - $sender = 'linus@stardew-tent-living.com'; - $recipient = 'pierre@general-store.com'; - $replyTo = null; - $calendar = $this->vCalendar3a; - // Act - $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipCancelWithInvalidMethod(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message contains an incorrect or invalid method'); - // construct parameters - $principalUri = 'principals/user/pierre'; - $sender = 'linus@stardew-tent-living.com'; - $recipient = 'pierre@general-store.com'; - $replyTo = null; - $calendar = $this->vCalendar3a; - $calendar->add('METHOD', 'UNKNOWN'); - // Act - $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipCancelWithNoEvent(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message contains no event'); - // construct parameters - $principalUri = 'principals/user/pierre'; - $sender = 'linus@stardew-tent-living.com'; - $recipient = 'pierre@general-store.com'; - $replyTo = null; - $calendar = $this->vCalendar3a; - $calendar->add('METHOD', 'CANCEL'); - $calendar->remove('VEVENT'); - // Act - $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipCancelWithNoUid(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message event dose not contains a UID'); - // construct parameters - $principalUri = 'principals/user/pierre'; - $sender = 'linus@stardew-tent-living.com'; - $recipient = 'pierre@general-store.com'; - $replyTo = null; - $calendar = $this->vCalendar3a; - $calendar->add('METHOD', 'CANCEL'); - $calendar->VEVENT->remove('UID'); - // Act - $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipCancelWithNoOrganizer(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message event dose not contains an organizer'); - // construct parameters - $principalUri = 'principals/user/pierre'; - $sender = 'linus@stardew-tent-living.com'; - $recipient = 'pierre@general-store.com'; - $replyTo = null; - $calendar = $this->vCalendar3a; - $calendar->add('METHOD', 'CANCEL'); - $calendar->VEVENT->remove('ORGANIZER'); - // Act - $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipCancelWithNoAttendee(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message event dose not contains any attendees'); - // construct parameters - $principalUri = 'principals/user/pierre'; - $sender = 'pierre@general-store.com'; - $recipient = 'linus@stardew-tent-living.com'; - $replyTo = null; - $calendar = $this->vCalendar3a; - $calendar->add('METHOD', 'CANCEL'); - $calendar->VEVENT->remove('ATTENDEE'); - // Act - $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendar->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipCancelAttendeeNotRecipient(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message event could not be processed because recipient must be an ATTENDEE of this event'); - // construct parameters - $principalUri = 'principals/user/pierre'; - $sender = 'linus@stardew-tent-living.com'; - $recipient = 'leah@general-store.com'; - $replyTo = null; - $calendarData = clone $this->vCalendar3a; - $calendarData->add('METHOD', 'CANCEL'); - // Act - $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipCancelDateInThePast(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct logger and time returns - $this->logger->expects(self::once())->method('warning') - ->with('iMip message event could not be processed because the event is in the past'); - $this->time->expects(self::once()) - ->method('getTime') - ->willReturn(time()); - // construct parameters - $principalUri = 'principals/user/pierre'; - $sender = 'linus@stardew-tent-living.com'; - $recipient = 'pierre@general-store.com'; - $replyTo = null; - $calendarData = clone $this->vCalendar3a; - $calendarData->add('METHOD', 'CANCEL'); - $calendarData->VEVENT->DTSTART = new \DateTime('2013-04-07'); // set to in the past - // Act - $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipCancelEventNotFound(): void { - // construct mock user calendar - $userCalendar = $this->createMock(ITestCalendar::class); - $userCalendar->expects(self::once()) - ->method('search') - ->willReturn([]); - // construct mock calendar manager and returns - /** @var Manager&MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods(['getCalendarsForPrincipal']) - ->getMock(); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->willReturn([$userCalendar]); - // construct time returns - $this->time->expects(self::once()) - ->method('getTime') - ->willReturn(1628374233); - // construct parameters - $principalUri = 'principals/user/pierre'; - $sender = 'linus@stardew-tent-living.com'; - $recipient = 'pierre@general-store.com'; - $replyTo = null; - $calendarData = clone $this->vCalendar3a; - $calendarData->add('METHOD', 'CANCEL'); - // construct logger return - $this->logger->expects(self::once())->method('warning') - ->with('iMip message event could not be processed because no corresponding event was found in any calendar', ['principalUri' => $principalUri, 'eventUid' => $calendarData->VEVENT->UID->getValue()]); - // Act - $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize()); - // Assert - $this->assertFalse($result); - } - - public function testHandleImipCancelOrganiserInReplyTo(): void { - /** @var Manager | \PHPUnit\Framework\MockObject\MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods([ - 'getCalendarsForPrincipal' - ]) - ->getMock(); - - $principalUri = 'principals/user/pierre'; - $sender = 'clint@stardew-tent-living.com'; - $recipient = 'pierre@general-store.com'; - $replyTo = 'linus@stardew-tent-living.com'; - $calendar = $this->createMock(ITestCalendar::class); - $calendarData = clone $this->vCalendar3a; - $calendarData->add('METHOD', 'CANCEL'); - - $this->time->expects(self::once()) - ->method('getTime') - ->willReturn(1628374233); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->with($principalUri) - ->willReturn([$calendar]); - $calendar->expects(self::once()) - ->method('search') - ->willReturn([['uri' => 'testname.ics']]); - $calendar->expects(self::once()) - ->method('handleIMipMessage') - ->with('testname.ics', $calendarData->serialize()); - // Act - $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize()); - // Assert - $this->assertTrue($result); - } - - public function testHandleImipCancel(): void { - /** @var Manager | \PHPUnit\Framework\MockObject\MockObject $manager */ - $manager = $this->getMockBuilder(Manager::class) - ->setConstructorArgs([ - $this->coordinator, - $this->container, - $this->logger, - $this->time, - $this->secureRandom, - $this->userManager, - $this->serverFactory, - ]) - ->onlyMethods([ - 'getCalendarsForPrincipal' - ]) - ->getMock(); - $principalUri = 'principals/user/pierre'; - $sender = 'linus@stardew-tent-living.com'; - $recipient = 'pierre@general-store.com'; - $replyTo = null; - $calendar = $this->createMock(ITestCalendar::class); - $calendarData = clone $this->vCalendar3a; - $calendarData->add('METHOD', 'CANCEL'); + ->method('handleIMip') + ->with('attendee3', $calendarData) + ->willReturn(true); - $this->time->expects(self::once()) - ->method('getTime') - ->willReturn(1628374233); - $manager->expects(self::once()) - ->method('getCalendarsForPrincipal') - ->with($principalUri) - ->willReturn([$calendar]); - $calendar->expects(self::once()) - ->method('search') - ->willReturn([['uri' => 'testname.ics']]); - $calendar->expects(self::once()) - ->method('handleIMipMessage') - ->with('testname.ics', $calendarData->serialize()); - // Act - $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize()); - // Assert + $result = $manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData); $this->assertTrue($result); } @@ -1622,11 +630,11 @@ private function getFreeBusyResponse(): string { - - mailto:admin@imap.localhost - - 2.0;Success - BEGIN:VCALENDAR + + mailto:admin@imap.localhost + + 2.0;Success + BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Sabre//Sabre VObject 4.5.6//EN CALSCALE:GREGORIAN @@ -1645,11 +653,11 @@ private function getFreeBusyResponse(): string { - - mailto:empty@imap.localhost - - 2.0;Success - BEGIN:VCALENDAR + + mailto:empty@imap.localhost + + 2.0;Success + BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Sabre//Sabre VObject 4.5.6//EN CALSCALE:GREGORIAN @@ -1666,11 +674,11 @@ private function getFreeBusyResponse(): string { - - mailto:user@imap.localhost - - 2.0;Success - BEGIN:VCALENDAR + + mailto:user@imap.localhost + + 2.0;Success + BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Sabre//Sabre VObject 4.5.6//EN CALSCALE:GREGORIAN @@ -1689,10 +697,10 @@ private function getFreeBusyResponse(): string { - - mailto:nouser@domain.tld - - 3.7;Could not find principal + + mailto:nouser@domain.tld + + 3.7;Could not find principal EOF;