Support iMIP invitations from Mail

Signed-off-by: Anna Larch <anna@nextcloud.com>
pull/33001/head
Anna Larch 4 years ago
parent 2576609aac
commit 4ca4b02793
  1. 4
      apps/dav/lib/CalDAV/CalDavBackend.php
  2. 1
      apps/dav/lib/CalDAV/CalendarHome.php
  3. 71
      apps/dav/lib/CalDAV/CalendarImpl.php
  4. 1
      apps/dav/lib/CalDAV/CalendarProvider.php
  5. 79
      apps/dav/tests/unit/CalDAV/CalendarImplTest.php
  6. 153
      lib/private/Calendar/Manager.php
  7. 9
      lib/public/Calendar/ICreateFromString.php
  8. 14
      lib/public/Calendar/IManager.php
  9. 377
      tests/lib/Calendar/ManagerTest.php

@ -1864,6 +1864,10 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
}
if(isset($options['uid'])) {
$outerQuery->andWhere($outerQuery->expr()->eq('uid', $outerQuery->createNamedParameter($options['uid'])));
}
if (!empty($options['types'])) {
$or = $outerQuery->expr()->orX();
foreach ($options['types'] as $type) {

@ -206,7 +206,6 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome {
return $this->caldavBackend->calendarSearch($principalUri, $filters, $limit, $offset);
}
public function enableCachedSubscriptionsForThisRequest() {
$this->returnCachedSubscriptions = true;
}

@ -29,10 +29,19 @@ namespace OCA\DAV\CalDAV;
use OCA\DAV\CalDAV\Auth\CustomPrincipalPlugin;
use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Calendar\Exceptions\CalendarException;
use OCP\Calendar\ICreateFromString;
use OCP\Constants;
use OCP\Security\ISecureRandom;
use Psr\Log\LoggerInterface;
use Sabre\DAV\Exception\Conflict;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VEvent;
use Sabre\VObject\Document;
use Sabre\VObject\ITip\Message;
use Sabre\VObject\Property\VCard\DateTime;
use Sabre\VObject\Reader;
use function Sabre\Uri\split as uriSplit;
class CalendarImpl implements ICreateFromString {
@ -46,13 +55,6 @@ class CalendarImpl implements ICreateFromString {
/** @var array */
private $calendarInfo;
/**
* CalendarImpl constructor.
*
* @param Calendar $calendar
* @param array $calendarInfo
* @param CalDavBackend $backend
*/
public function __construct(Calendar $calendar,
array $calendarInfo,
CalDavBackend $backend) {
@ -177,4 +179,59 @@ class CalendarImpl implements ICreateFromString {
fclose($stream);
}
}
/**
* @throws CalendarException
*/
public function handleIMipMessage(string $name, string $calendarData): void {
$server = new InvitationResponseServer(false);
/** @var CustomPrincipalPlugin $plugin */
$plugin = $server->server->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');
}
// Force calendar change URI
/** @var Schedule\Plugin $schedulingPlugin */
$schedulingPlugin = $server->server->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');
}
if(!isset($vEvent->{'ORGANIZER'}) || !isset($vEvent->{'ATTENDEE'})) {
throw new CalendarException('Could not process scheduling data, neccessary data missing from ICAL');
}
$orgaizer = $vEvent->{'ORGANIZER'}->getValue();
$attendee = $vEvent->{'ATTENDEE'}->getValue();
$iTipMessage->method = $vObject->{'METHOD'}->getValue();
if($iTipMessage->method === 'REPLY') {
if ($server->isExternalAttendee($vEvent->{'ATTENDEE'}->getValue())) {
$iTipMessage->recipient = $orgaizer;
} else {
$iTipMessage->recipient = $attendee;
}
$iTipMessage->sender = $attendee;
} else if($iTipMessage->method === 'CANCEL') {
$iTipMessage->recipient = $attendee;
$iTipMessage->sender = $orgaizer;
}
$iTipMessage->uid = isset($vEvent->{'UID'}) ? $vEvent->{'UID'}->getValue() : '';
$iTipMessage->component = 'VEVENT';
$iTipMessage->sequence = isset($vEvent->{'SEQUENCE'}) ? (int)$vEvent->{'SEQUENCE'}->getValue() : 0;
$iTipMessage->message = $vObject;
$schedulingPlugin->scheduleLocalDelivery($iTipMessage);
}
}

@ -26,6 +26,7 @@ declare(strict_types=1);
namespace OCA\DAV\CalDAV;
use OCP\Calendar\ICalendarProvider;
use OCP\Calendar\ICreateFromString;
use OCP\IConfig;
use OCP\IL10N;
use Psr\Log\LoggerInterface;

@ -25,9 +25,13 @@
*/
namespace OCA\DAV\Tests\unit\CalDAV;
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 PHPUnit\Framework\MockObject\MockObject;
class CalendarImplTest extends \Test\TestCase {
@ -51,6 +55,7 @@ class CalendarImplTest extends \Test\TestCase {
'id' => 'fancy_id_123',
'{DAV:}displayname' => 'user readable name 123',
'{http://apple.com/ns/ical/}calendar-color' => '#AABBCC',
'uri' => '/this/is/a/uri'
];
$this->backend = $this->createMock(CalDavBackend::class);
@ -125,4 +130,78 @@ class CalendarImplTest extends \Test\TestCase {
$this->assertEquals(31, $this->calendarImpl->getPermissions());
}
public function testHandleImipMessage(): void {
$invitationResponseServer = $this->createConfiguredMock(InvitationResponseServer::class, [
'server' => $this->createConfiguredMock(CalDavBackend::class, [
'getPlugin' => [
'auth' => $this->createMock(CustomPrincipalPlugin::class),
'schedule' => $this->createMock(Plugin::class)
]
])
]);
$message = <<<EOF
BEGIN:VCALENDAR
PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
METHOD:REPLY
VERSION:2.0
BEGIN:VEVENT
ATTENDEE;PARTSTAT=mailto:lewis@stardew-tent-living.com:ACCEPTED
ORGANIZER:mailto:pierre@generalstore.com
UID:aUniqueUid
SEQUENCE:2
REQUEST-STATUS:2.0;Success
%sEND:VEVENT
END:VCALENDAR
EOF;
/** @var CustomPrincipalPlugin|MockObject $authPlugin */
$authPlugin = $invitationResponseServer->server->getPlugin('auth');
$authPlugin->expects(self::once())
->method('setPrincipalUri')
->with($this->calendar->getPrincipalURI());
/** @var Plugin|MockObject $schedulingPlugin */
$schedulingPlugin = $invitationResponseServer->server->getPlugin('caldav-schedule');
$schedulingPlugin->expects(self::once())
->method('setPathOfCalendarObjectChange')
->with('fullcalendarname');
}
public function testHandleImipMessageNoCalendarUri(): void {
$invitationResponseServer = $this->createConfiguredMock(InvitationResponseServer::class, [
'server' => $this->createConfiguredMock(CalDavBackend::class, [
'getPlugin' => [
'auth' => $this->createMock(CustomPrincipalPlugin::class),
'schedule' => $this->createMock(Plugin::class)
]
])
]);
$message = <<<EOF
BEGIN:VCALENDAR
PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
METHOD:REPLY
VERSION:2.0
BEGIN:VEVENT
ATTENDEE;PARTSTAT=mailto:lewis@stardew-tent-living.com:ACCEPTED
ORGANIZER:mailto:pierre@generalstore.com
UID:aUniqueUid
SEQUENCE:2
REQUEST-STATUS:2.0;Success
%sEND:VEVENT
END:VCALENDAR
EOF;
/** @var CustomPrincipalPlugin|MockObject $authPlugin */
$authPlugin = $invitationResponseServer->server->getPlugin('auth');
$authPlugin->expects(self::once())
->method('setPrincipalUri')
->with($this->calendar->getPrincipalURI());
unset($this->calendarInfo['uri']);
$this->expectException('CalendarException');
$this->calendarImpl->handleIMipMessage('filename.ics', $message);
}
}

@ -27,12 +27,19 @@ declare(strict_types=1);
namespace OC\Calendar;
use OC\AppFramework\Bootstrap\Coordinator;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Calendar\Exceptions\CalendarException;
use OCP\Calendar\ICalendar;
use OCP\Calendar\ICalendarProvider;
use OCP\Calendar\ICalendarQuery;
use OCP\Calendar\ICreateFromString;
use OCP\Calendar\IManager;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VEvent;
use Sabre\VObject\Property\VCard\DateTime;
use Sabre\VObject\Reader;
use Throwable;
use function array_map;
use function array_merge;
@ -58,12 +65,17 @@ class Manager implements IManager {
/** @var LoggerInterface */
private $logger;
private ITimeFactory $timeFactory;
public function __construct(Coordinator $coordinator,
ContainerInterface $container,
LoggerInterface $logger) {
LoggerInterface $logger,
ITimeFactory $timeFactory) {
$this->coordinator = $coordinator;
$this->container = $container;
$this->logger = $logger;
$this->timeFactory = $timeFactory;
}
/**
@ -167,6 +179,11 @@ class Manager implements IManager {
$this->calendarLoaders = [];
}
/**
* @param string $principalUri
* @param array $calendarUris
* @return ICreateFromString[]
*/
public function getCalendarsForPrincipal(string $principalUri, array $calendarUris = []): array {
$context = $this->coordinator->getRegistrationContext();
if ($context === null) {
@ -198,7 +215,6 @@ class Manager implements IManager {
);
$results = [];
/** @var ICalendar $calendar */
foreach ($calendars as $calendar) {
$r = $calendar->search(
$query->getSearchPattern() ?? '',
@ -219,4 +235,137 @@ class Manager implements IManager {
public function newQuery(string $principalUri): ICalendarQuery {
return new CalendarQuery($principalUri);
}
/**
* @throws \OCP\DB\Exception
*/
public function handleIMipReply(string $principalUri, string $sender, string $recipient, string $calendarData): bool {
/** @var VCalendar $vObject */
$vObject = Reader::read($calendarData);
/** @var VEvent $vEvent */
$vEvent = $vObject->{'VEVENT'};
// First, we check if the correct method is passed to us
if (strcasecmp('REPLY', $vObject->{'METHOD'}->getValue()) !== 0) {
$this->logger->warning('Wrong method provided for processing');
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('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('Only events in the future are processed');
return false;
}
$calendars = $this->getCalendarsForPrincipal($principalUri);
if (empty($calendars)) {
$this->logger->warning('Could not find any calendars for principal ' . $principalUri);
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 ICreateFromString) {
$o = $calendar->search($sender, ['ATTENDEE'], ['uid' => $vEvent->{'UID'}->getValue()]);
if (!empty($o)) {
$found = $calendar;
$name = $o[0]['uri'];
break;
}
}
}
if (empty($found)) {
$this->logger->info('Event not found in any calendar for principal ' . $principalUri . 'and UID' . $vEvent->{'UID'}->getValue());
return false;
}
try {
$found->handleIMipMessage($name, $calendarData); // sabre will handle the scheduling behind the scenes
} catch (CalendarException $e) {
$this->logger->error('Could not update calendar for iMIP processing', ['exception' => $e]);
return false;
}
return true;
}
/**
* @since 25.0.0
* @throws \OCP\DB\Exception
*/
public function handleIMipCancel(string $principalUri, string $sender, ?string $replyTo, string $recipient, string $calendarData): bool {
$vObject = Reader::read($calendarData);
/** @var VEvent $vEvent */
$vEvent = $vObject->{'VEVENT'};
// First, we check if the correct method is passed to us
if (strcasecmp('CANCEL', $vObject->{'METHOD'}->getValue()) !== 0) {
$this->logger->warning('Wrong method provided for processing');
return false;
}
$attendee = substr($vEvent->{'ATTENDEE'}->getValue(), 7);
if (strcasecmp($recipient, $attendee) !== 0) {
$this->logger->warning('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);
if (strcasecmp($sender, $organizer) !== 0 && strcasecmp($replyTo, $organizer) !== 0) {
$this->logger->warning('Sender must be the ORGANIZER of this event');
return false;
}
$calendars = $this->getCalendarsForPrincipal($principalUri);
$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 ICreateFromString) {
$o = $calendar->search($recipient, ['ATTENDEE'], ['uid' => $vEvent->{'UID'}->getValue()]);
if (!empty($o)) {
$found = $calendar;
$name = $o[0]['uri'];
break;
}
}
}
if (empty($found)) {
$this->logger->info('Event not found in any calendar for principal ' . $principalUri . 'and UID' . $vEvent->{'UID'}->getValue());
// this is a safe operation
// we can ignore events that have been cancelled but were not in the calendar anyway
return true;
}
try {
$found->handleIMipMessage($name, $calendarData); // sabre will handle the scheduling behind the scenes
return true;
} catch (CalendarException $e) {
$this->logger->error('Could not update calendar for iMIP processing', ['exception' => $e]);
return false;
}
}
}

@ -1,4 +1,6 @@
<?php
declare(strict_types=1);
/**
* @copyright 2021 Anna Larch <anna.larch@gmx.net>
*
@ -38,4 +40,11 @@ interface ICreateFromString extends ICalendar {
* @throws CalendarException
*/
public function createFromString(string $name, string $calendarData): void;
/**
* @since 25.0.0
*
* @throws CalendarException
*/
public function handleIMipMessage(string $name, string $calendarData): void;
}

@ -156,4 +156,18 @@ interface IManager {
* @since 23.0.0
*/
public function newQuery(string $principalUri) : ICalendarQuery;
/**
* Handle a iMip REPLY message
*
* @since 25.0.0
*/
public function handleIMipReply(string $principalUri, string $sender, string $recipient, string $calendarData): bool;
/**
* Handle a iMip CANCEL message
*
* @since 25.0.0
*/
public function handleIMipCancel(string $principalUri, string $sender, ?string $replyTo, string $recipient, string $calendarData): bool;
}

@ -25,10 +25,14 @@ namespace Test\Calendar;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\Calendar\Manager;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Calendar\ICalendar;
use OCP\Calendar\ICreateFromString;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Sabre\VObject\Document;
use Sabre\VObject\Reader;
use Test\TestCase;
class ManagerTest extends TestCase {
@ -45,17 +49,22 @@ class ManagerTest extends TestCase {
/** @var Manager */
private $manager;
/** @var ITimeFactory|ITimeFactory&MockObject|MockObject */
private $time;
protected function setUp(): void {
parent::setUp();
$this->coordinator = $this->createMock(Coordinator::class);
$this->container = $this->createMock(ContainerInterface::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->time = $this->createMock(ITimeFactory::class);
$this->manager = new Manager(
$this->coordinator,
$this->container,
$this->logger
$this->logger,
$this->time,
);
}
@ -231,4 +240,370 @@ class ManagerTest extends TestCase {
$isEnabled = $this->manager->isEnabled();
$this->assertTrue($isEnabled);
}
public function testHandleImipReplyWrongMethod(): void {
$principalUri = 'principals/user/linus';
$sender = 'pierre@general-store.com';
$recipient = 'linus@stardew-tent-living.com';
$calendarData = $this->getVCalendarReply();
$calendarData->METHOD = 'REQUEST';
$this->logger->expects(self::once())
->method('warning');
$this->time->expects(self::never())
->method('getTimestamp');
$result = $this->manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData->serialize());
$this->assertFalse($result);
}
public function testHandleImipReplyOrganizerNotRecipient(): void {
$principalUri = 'principals/user/linus';
$recipient = 'pierre@general-store.com';
$sender = 'linus@stardew-tent-living.com';
$calendarData = $this->getVCalendarReply();
$this->logger->expects(self::once())
->method('warning');
$this->time->expects(self::never())
->method('getTimestamp');
$result = $this->manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData->serialize());
$this->assertFalse($result);
}
public function testHandleImipReplyDateInThePast(): void {
$principalUri = 'principals/user/linus';
$sender = 'pierre@general-store.com';
$recipient = 'linus@stardew-tent-living.com';
$calendarData = $this->getVCalendarReply();
$calendarData->VEVENT->DTSTART = new \DateTime('2013-04-07'); // set to in the past
$this->time->expects(self::once())
->method('getTimestamp')
->willReturn(time());
$this->logger->expects(self::once())
->method('warning');
$result = $this->manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData->serialize());
$this->assertFalse($result);
}
public function testHandleImipReplyNoCalendars(): void {
/** @var Manager | \PHPUnit\Framework\MockObject\MockObject $manager */
$manager = $this->getMockBuilder(Manager::class)
->setConstructorArgs([
$this->coordinator,
$this->container,
$this->logger,
$this->time
])
->setMethods([
'getCalendarsForPrincipal'
]);
$principalUri = 'principals/user/linus';
$sender = 'pierre@general-store.com';
$recipient = 'linus@stardew-tent-living.com';
$calendarData = $this->getVCalendarReply();
$this->time->expects(self::once())
->method('getTimestamp')
->willReturn(202208219);
$manager->expects(self::once())
->method('getCalendarsForPrincipal')
->willReturn([]);
$this->logger->expects(self::once())
->method('warning');
$result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData->serialize());
$this->assertFalse($result);
}
public function testHandleImipReplyEventNotFound(): void {
/** @var Manager | \PHPUnit\Framework\MockObject\MockObject $manager */
$manager = $this->getMockBuilder(Manager::class)
->setConstructorArgs([
$this->coordinator,
$this->container,
$this->logger,
$this->time
])
->setMethods([
'getCalendarsForPrincipal'
]);
$calendar = $this->createMock(ICreateFromString::class);
$principalUri = 'principals/user/linus';
$sender = 'pierre@general-store.com';
$recipient = 'linus@stardew-tent-living.com';
$calendarData = $this->getVCalendarReply();
$this->time->expects(self::once())
->method('getTimestamp')
->willReturn(202208219);
$manager->expects(self::once())
->method('getCalendarsForPrincipal')
->willReturn([$calendar]);
$calendar->expects(self::once())
->method('search')
->willReturn([]);
$this->logger->expects(self::once())
->method('info');
$calendar->expects(self::never())
->method('handleIMipMessage');
$result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData->serialize());
$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
])
->setMethods([
'getCalendarsForPrincipal'
]);
$calendar = $this->createMock(ICreateFromString::class);
$principalUri = 'principals/user/linus';
$sender = 'pierre@general-store.com';
$recipient = 'linus@stardew-tent-living.com';
$calendarData = $this->getVCalendarReply();
$this->time->expects(self::once())
->method('getTimestamp')
->willReturn(202208219);
$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());
$result = $manager->handleIMipReply($principalUri, $sender, $recipient, $calendarData->serialize());
$this->assertTrue($result);
}
public function testHandleImipCancelWrongMethod(): void {
$principalUri = 'principals/user/pierre';
$sender = 'linus@stardew-tent-living.com';
$recipient = 'pierre@general-store.com';
$replyTo = null;
$calendarData = $this->getVCalendarCancel();
$calendarData->VEVENT->METHOD = 'REQUEST';
$this->logger->expects(self::once())
->method('warning');
$this->time->expects(self::never())
->method('getTimestamp');
$result = $this->manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
$this->assertFalse($result);
}
public function testHandleImipCancelAttendeeNotRecipient(): void {
$principalUri = '/user/admin';
$sender = 'linus@stardew-tent-living.com';
$recipient = 'leah@general-store.com';
$replyTo = null;
$calendarData = $this->getVCalendarCancel();
$calendarData->VEVENT->METHOD = 'CANCEL';
$this->logger->expects(self::once())
->method('warning');
$this->time->expects(self::never())
->method('getTimestamp');
$result = $this->manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
$this->assertFalse($result);
}
public function testHandleImipCancelDateInThePast(): void {
$principalUri = 'principals/user/pierre';
$sender = 'linus@stardew-tent-living.com';
$recipient = 'pierre@general-store.com';
$replyTo = null;
$calendarData = $this->getVCalendarCancel();
$calendarData->VEVENT->DTSTART = new \DateTime('2013-04-07'); // set to in the past
$this->time->expects(self::once())
->method('getTimestamp')
->willReturn(time());
$this->logger->expects(self::once())
->method('warning');
$result = $this->manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
$this->assertFalse($result);
}
public function testHandleImipCancelNoCalendars(): void {
/** @var Manager | \PHPUnit\Framework\MockObject\MockObject $manager */
$manager = $this->getMockBuilder(Manager::class)
->setConstructorArgs([
$this->coordinator,
$this->container,
$this->logger,
$this->time
])
->setMethods([
'getCalendarsForPrincipal'
]);
$principalUri = 'principals/user/pierre';
$sender = 'linus@stardew-tent-living.com';
$recipient = 'pierre@general-store.com';
$replyTo = null;
$calendarData = $this->getVCalendarCancel();
$this->time->expects(self::once())
->method('getTimestamp')
->willReturn(202208219);
$manager->expects(self::once())
->method('getCalendarsForPrincipal')
->with($principalUri)
->willReturn([]);
$this->logger->expects(self::once())
->method('warning');
$result = $this->manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
$this->assertTrue($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
])
->setMethods([
'getCalendarsForPrincipal'
]);
$principalUri = 'principals/user/pierre';
$sender = 'clint@stardew-blacksmiths.com';
$recipient = 'pierre@general-store.com';
$replyTo = 'linus@stardew-tent-living.com';
$calendar = $this->createMock(ICreateFromString::class);
$calendarData = $this->getVCalendarCancel();
$calendarData->VEVENT->METHOD = 'CANCEL';
$this->time->expects(self::once())
->method('getTimestamp')
->willReturn(202208219);
$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());
$result = $this->manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
$this->assertFalse($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
])
->setMethods([
'getCalendarsForPrincipal'
]);
$principalUri = 'principals/user/pierre';
$sender = 'linus@stardew-tent-living.com';
$recipient = 'pierre@general-store.com';
$replyTo = null;
$calendar = $this->createMock(ICreateFromString::class);
$calendarData = $this->getVCalendarCancel();
$calendarData->VEVENT->METHOD = 'CANCEL';
$this->time->expects(self::once())
->method('getTimestamp')
->willReturn(202208219);
$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());
$result = $this->manager->handleIMipCancel($principalUri, $sender, $replyTo, $recipient, $calendarData->serialize());
$this->assertFalse($result);
}
private function getVCalendarReply(): Document {
$data = <<<EOF
BEGIN:VCALENDAR
PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:REPLY
BEGIN:VEVENT
DTSTART;VALUE=DATE:20210820
DTEND;VALUE=DATE:20220821
DTSTAMP:20210812T100040Z
ORGANIZER;CN=admin:mailto:linus@stardew-tent-living.com
UID:dcc733bf-b2b2-41f2-a8cf-550ae4b67aff
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=pierr
e@general-store.com;X-NUM-GUESTS=0:mailto:pierre@general-store.com
CREATED:20220812T100021Z
DESCRIPTION:
LAST-MODIFIED:20220812T100040Z
LOCATION:
SEQUENCE:3
STATUS:CONFIRMED
SUMMARY:berry basket
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR
EOF;
return Reader::read($data);
}
private function getVCalendarCancel(): Document {
$data = <<<EOF
BEGIN:VCALENDAR
PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:CANCEL
BEGIN:VEVENT
DTSTART;VALUE=DATE:20210820
DTEND;VALUE=DATE:20220821
DTSTAMP:20210812T100040Z
ORGANIZER;CN=admin:mailto:linus@stardew-tent-living.com
UID:dcc733bf-b2b2-41f2-a8cf-550ae4b67aff
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=pierr
e@general-store.com;X-NUM-GUESTS=0:mailto:pierre@general-store.com
CREATED:20220812T100021Z
DESCRIPTION:
LAST-MODIFIED:20220812T100040Z
LOCATION:
SEQUENCE:3
STATUS:CANCELLED
SUMMARY:berry basket
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR
EOF;
return Reader::read($data);
}
}

Loading…
Cancel
Save