mirror of
https://github.com/nextcloud/server.git
synced 2026-04-15 22:11:17 -04:00
Support iMIP invitations from Mail
Signed-off-by: Anna Larch <anna@nextcloud.com>
This commit is contained in:
parent
2576609aac
commit
4ca4b02793
9 changed files with 698 additions and 11 deletions
|
|
@ -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…
Reference in a new issue