Skip to content

Commit 29922f7

Browse files
committed
fix scheduling plugin not updating responding attendee status
Signed-off-by: Anna Larch <anna@nextcloud.com>
1 parent 9be9393 commit 29922f7

File tree

1 file changed

+216
-52
lines changed

1 file changed

+216
-52
lines changed

apps/dav/lib/CalDAV/Schedule/Plugin.php

Lines changed: 216 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@
3131
use DateTimeZone;
3232
use OCA\DAV\CalDAV\CalDavBackend;
3333
use OCA\DAV\CalDAV\CalendarHome;
34+
use OCA\DAV\Connector\Sabre\DavAclPlugin;
3435
use OCP\IConfig;
36+
use Sabre\CalDAV\Calendar;
37+
use Sabre\CalDAV\CalendarObject;
3538
use Sabre\CalDAV\ICalendar;
39+
use Sabre\CalDAV\Schedule\Inbox;
3640
use Sabre\DAV\INode;
3741
use Sabre\DAV\IProperties;
3842
use Sabre\DAV\PropFind;
@@ -117,59 +121,9 @@ public function propFind(PropFind $propFind, INode $node) {
117121
}
118122

119123
/**
120-
* Returns a list of addresses that are associated with a principal.
121-
*
122-
* @param string $principal
123-
* @return array
124-
*/
125-
protected function getAddressesForPrincipal($principal) {
126-
$result = parent::getAddressesForPrincipal($principal);
127-
128-
if ($result === null) {
129-
$result = [];
130-
}
131-
132-
return $result;
133-
}
134-
135-
/**
136-
* @param RequestInterface $request
137-
* @param ResponseInterface $response
138-
* @param VCalendar $vCal
139-
* @param mixed $calendarPath
140-
* @param mixed $modified
141-
* @param mixed $isNew
142-
*/
143-
public function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) {
144-
// Save the first path we get as a calendar-object-change request
145-
if (!$this->pathOfCalendarObjectChange) {
146-
$this->pathOfCalendarObjectChange = $request->getPath();
147-
}
148-
149-
parent::calendarObjectChange($request, $response, $vCal, $calendarPath, $modified, $isNew);
150-
}
151-
152-
/**
153-
* @inheritDoc
124+
* @param ITip\Message $iTipMessage
154125
*/
155-
public function scheduleLocalDelivery(ITip\Message $iTipMessage):void {
156-
parent::scheduleLocalDelivery($iTipMessage);
157-
158-
// We only care when the message was successfully delivered locally
159-
if ($iTipMessage->scheduleStatus !== '1.2;Message delivered locally') {
160-
return;
161-
}
162-
163-
// We only care about request. reply and cancel are properly handled
164-
// by parent::scheduleLocalDelivery already
165-
if (strcasecmp($iTipMessage->method, 'REQUEST') !== 0) {
166-
return;
167-
}
168-
169-
// If parent::scheduleLocalDelivery set scheduleStatus to 1.2,
170-
// it means that it was successfully delivered locally.
171-
// Meaning that the ACL plugin is loaded and that a principial
172-
// exists for the given recipient id, no need to double check
126+
public function processResources(ITip\Message $iTipMessage): void {
173127
/** @var \Sabre\DAVACL\Plugin $aclPlugin */
174128
$aclPlugin = $this->server->getPlugin('acl');
175129
$principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient);
@@ -202,6 +156,7 @@ public function scheduleLocalDelivery(ITip\Message $iTipMessage):void {
202156
$vevent = $vcalendar->VEVENT;
203157

204158
// We don't support autoresponses for recurrencing events for now
159+
// @todo does this need to be fixed for resource booking?
205160
if (isset($vevent->RRULE) || isset($vevent->RDATE)) {
206161
return;
207162
}
@@ -257,6 +212,64 @@ public function scheduleLocalDelivery(ITip\Message $iTipMessage):void {
257212
$this->schedulingResponses[] = $responseITipMessage;
258213
}
259214

215+
/**
216+
* Returns a list of addresses that are associated with a principal.
217+
*
218+
* @param string $principal
219+
* @return array
220+
*/
221+
protected function getAddressesForPrincipal($principal) {
222+
$result = parent::getAddressesForPrincipal($principal);
223+
224+
if ($result === null) {
225+
$result = [];
226+
}
227+
228+
return $result;
229+
}
230+
231+
/**
232+
* @param RequestInterface $request
233+
* @param ResponseInterface $response
234+
* @param VCalendar $vCal
235+
* @param mixed $calendarPath
236+
* @param mixed $modified
237+
* @param mixed $isNew
238+
*/
239+
public function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) {
240+
// Save the first path we get as a calendar-object-change request
241+
if (!$this->pathOfCalendarObjectChange) {
242+
$this->pathOfCalendarObjectChange = $request->getPath();
243+
}
244+
245+
parent::calendarObjectChange($request, $response, $vCal, $calendarPath, $modified, $isNew);
246+
}
247+
248+
/**
249+
* @inheritDoc
250+
*/
251+
public function scheduleLocalDelivery(ITip\Message $iTipMessage):void {
252+
$this->process($iTipMessage);
253+
254+
// We only care when the message was successfully delivered locally
255+
if ($iTipMessage->scheduleStatus !== '1.2;Message delivered locally') {
256+
// we're missing the additional processing of a schedule message to the responding attendee's scheduling inbox
257+
return;
258+
}
259+
260+
// We only care about request. reply and cancel are properly handled
261+
// by $this->process already
262+
if (strcasecmp($iTipMessage->method, 'REQUEST') !== 0) {
263+
return;
264+
}
265+
266+
// If $this->process set scheduleStatus to 1.2,
267+
// it means that it was successfully delivered locally.
268+
// Meaning that the ACL plugin is loaded and that a principial
269+
// exists for the given recipient id, no need to double check
270+
$this->processResources($iTipMessage);
271+
}
272+
260273
/**
261274
* @param string $uri
262275
*/
@@ -554,4 +567,155 @@ private function stripOffMailTo(string $email): string {
554567

555568
return $email;
556569
}
570+
571+
private function process(ITip\Message $iTipMessage) {
572+
/** @var DavAclPlugin $aclPlugin */
573+
$aclPlugin = $this->server->getPlugin('acl');
574+
575+
// Local delivery is not available if the ACL plugin is not loaded.
576+
//
577+
if (!$aclPlugin) {
578+
return;
579+
}
580+
581+
$caldavNS = '{'.self::NS_CALDAV.'}';
582+
583+
//before all the exciting stuff happens, the attendee needs to be updated.
584+
585+
// look for the organizer principal
586+
$principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient);
587+
if (!$principalUri) {
588+
$iTipMessage->scheduleStatus = '3.7;Could not find principal.';
589+
590+
return;
591+
}
592+
593+
// We found a principal URL, now we need to find its inbox.
594+
// Unfortunately we may not have sufficient privileges to find this, so
595+
// we are temporarily turning off ACL to let this come through.
596+
//
597+
// Once we support PHP 5.5, this should be wrapped in a try..finally
598+
// block so we can ensure that this privilege gets added again after.
599+
$this->server->removeListener('propFind', [$aclPlugin, 'propFind']);
600+
601+
$result = $this->server->getProperties(
602+
$principalUri,
603+
[
604+
'{DAV:}principal-URL',
605+
$caldavNS.'calendar-home-set',
606+
$caldavNS.'schedule-inbox-URL',
607+
$caldavNS.'schedule-default-calendar-URL',
608+
'{http://sabredav.org/ns}email-address',
609+
]
610+
);
611+
612+
// Re-registering the ACL event
613+
$this->server->on('propFind', [$aclPlugin, 'propFind'], 20);
614+
615+
if (!isset($result[$caldavNS.'schedule-inbox-URL'])) {
616+
$iTipMessage->scheduleStatus = '5.2;Could not find local inbox';
617+
618+
return;
619+
}
620+
if (!isset($result[$caldavNS.'calendar-home-set'])) {
621+
$iTipMessage->scheduleStatus = '5.2;Could not locate a calendar-home-set';
622+
623+
return;
624+
}
625+
if (!isset($result[$caldavNS.'schedule-default-calendar-URL'])) {
626+
$iTipMessage->scheduleStatus = '5.2;Could not find a schedule-default-calendar-URL property';
627+
628+
return;
629+
}
630+
631+
$calendarPath = $result[$caldavNS.'schedule-default-calendar-URL']->getHref();
632+
$homePath = $result[$caldavNS.'calendar-home-set']->getHref();
633+
$inboxPath = $result[$caldavNS.'schedule-inbox-URL']->getHref();
634+
635+
if ('REPLY' === $iTipMessage->method) {
636+
$privilege = 'schedule-deliver-reply';
637+
} else {
638+
$privilege = 'schedule-deliver-invite';
639+
}
640+
641+
if (!$aclPlugin->checkPrivileges($inboxPath, $caldavNS.$privilege, \Sabre\DAVACL\Plugin::R_PARENT, false)) {
642+
$iTipMessage->scheduleStatus = '3.8;insufficient privileges: '.$privilege.' is required on the recipient schedule inbox.';
643+
return;
644+
}
645+
646+
// Next, we're going to find out if the item already exits in one of
647+
// the users' calendars.
648+
$uid = $iTipMessage->uid;
649+
650+
$newFileName = 'sabredav-'.\Sabre\DAV\UUIDUtil::getUUID().'.ics';
651+
652+
/** @var CalendarHome $home */
653+
$home = $this->server->tree->getNodeForPath($homePath);
654+
/** @var Inbox $inbox */
655+
$inbox = $this->server->tree->getNodeForPath($inboxPath);
656+
657+
$currentObject = null;
658+
$objectNode = null;
659+
$oldICalendarData = null;
660+
$isNewNode = false;
661+
662+
$result = $home->getCalendarObjectByUID($uid);
663+
if ($result) {
664+
// There was an existing object, we need to update probably.
665+
$objectPath = $homePath.'/'.$result;
666+
/** @var CalendarObject $objectNode */
667+
$objectNode = $this->server->tree->getNodeForPath($objectPath);
668+
$oldICalendarData = $objectNode->get();
669+
$currentObject = Reader::read($oldICalendarData);
670+
} else {
671+
$isNewNode = true;
672+
}
673+
674+
$broker = new ITip\Broker();
675+
$newObject = $broker->processMessage($iTipMessage, $currentObject);
676+
677+
// create a new ics file for organizer with the new attendee status
678+
$inbox->createFile($newFileName, $iTipMessage->message->serialize());
679+
680+
if (!$newObject) {
681+
// We received an iTip message referring to a UID that we don't
682+
// have in any calendars yet, and processMessage did not give us a
683+
// calendarobject back.
684+
//
685+
// The implication is that processMessage did not understand the
686+
// iTip message.
687+
$iTipMessage->scheduleStatus = '5.0;iTip message was not processed by the server, likely because we didn\'t understand it.';
688+
689+
return;
690+
}
691+
692+
// Note that we are bypassing ACL on purpose by calling this directly.
693+
// We may need to look a bit deeper into this later. Supporting ACL
694+
// here would be nice.
695+
if ($isNewNode) {
696+
/** @var Calendar $calendar */
697+
$calendar = $this->server->tree->getNodeForPath($calendarPath);
698+
/** @var resource $resource */
699+
$resource = $newObject->serialize();
700+
$calendar->createFile($newFileName, $resource);
701+
} else {
702+
// If the message was a reply, we may have to inform other
703+
// attendees of this attendees status. Therefore we're shooting off
704+
// another itipMessage.
705+
if ('REPLY' === $iTipMessage->method) {
706+
$this->processICalendarChange(
707+
$oldICalendarData,
708+
$newObject,
709+
[$iTipMessage->recipient],
710+
// this used to have the sender in the ignore field
711+
// removed that because creating a scheduling update for the
712+
// attendee would mean duplicating all this code
713+
// this is not RFC6638 conform (See Section 4.2), but it's working
714+
[]
715+
);
716+
}
717+
$objectNode->put($newObject->serialize());
718+
}
719+
$iTipMessage->scheduleStatus = '1.2;Message delivered locally';
720+
}
557721
}

0 commit comments

Comments
 (0)