Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scheduling plugin not updating responding attendee status #28094

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
268 changes: 216 additions & 52 deletions apps/dav/lib/CalDAV/Schedule/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@
use DateTimeZone;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\CalendarHome;
use OCA\DAV\Connector\Sabre\DavAclPlugin;
use OCP\IConfig;
use Sabre\CalDAV\Calendar;
use Sabre\CalDAV\CalendarObject;
use Sabre\CalDAV\ICalendar;
use Sabre\CalDAV\Schedule\Inbox;
use Sabre\DAV\INode;
use Sabre\DAV\IProperties;
use Sabre\DAV\PropFind;
Expand Down Expand Up @@ -117,59 +121,9 @@ public function propFind(PropFind $propFind, INode $node) {
}

/**
* Returns a list of addresses that are associated with a principal.
*
* @param string $principal
* @return array
*/
protected function getAddressesForPrincipal($principal) {
$result = parent::getAddressesForPrincipal($principal);

if ($result === null) {
$result = [];
}

return $result;
}

/**
* @param RequestInterface $request
* @param ResponseInterface $response
* @param VCalendar $vCal
* @param mixed $calendarPath
* @param mixed $modified
* @param mixed $isNew
*/
public function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) {
// Save the first path we get as a calendar-object-change request
if (!$this->pathOfCalendarObjectChange) {
$this->pathOfCalendarObjectChange = $request->getPath();
}

parent::calendarObjectChange($request, $response, $vCal, $calendarPath, $modified, $isNew);
}

/**
* @inheritDoc
* @param ITip\Message $iTipMessage
*/
public function scheduleLocalDelivery(ITip\Message $iTipMessage):void {
parent::scheduleLocalDelivery($iTipMessage);

// We only care when the message was successfully delivered locally
if ($iTipMessage->scheduleStatus !== '1.2;Message delivered locally') {
return;
}

// We only care about request. reply and cancel are properly handled
// by parent::scheduleLocalDelivery already
if (strcasecmp($iTipMessage->method, 'REQUEST') !== 0) {
return;
}

// If parent::scheduleLocalDelivery set scheduleStatus to 1.2,
// it means that it was successfully delivered locally.
// Meaning that the ACL plugin is loaded and that a principial
// exists for the given recipient id, no need to double check
public function processResources(ITip\Message $iTipMessage): void {
/** @var \Sabre\DAVACL\Plugin $aclPlugin */
$aclPlugin = $this->server->getPlugin('acl');
$principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient);
Expand Down Expand Up @@ -202,6 +156,7 @@ public function scheduleLocalDelivery(ITip\Message $iTipMessage):void {
$vevent = $vcalendar->VEVENT;

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

/**
* Returns a list of addresses that are associated with a principal.
*
* @param string $principal
* @return array
*/
protected function getAddressesForPrincipal($principal) {
$result = parent::getAddressesForPrincipal($principal);

if ($result === null) {
$result = [];
}

return $result;
}

/**
* @param RequestInterface $request
* @param ResponseInterface $response
* @param VCalendar $vCal
* @param mixed $calendarPath
* @param mixed $modified
* @param mixed $isNew
*/
public function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) {
// Save the first path we get as a calendar-object-change request
if (!$this->pathOfCalendarObjectChange) {
$this->pathOfCalendarObjectChange = $request->getPath();
}

parent::calendarObjectChange($request, $response, $vCal, $calendarPath, $modified, $isNew);
}

/**
* @inheritDoc
*/
public function scheduleLocalDelivery(ITip\Message $iTipMessage):void {
$this->process($iTipMessage);

// We only care when the message was successfully delivered locally
if ($iTipMessage->scheduleStatus !== '1.2;Message delivered locally') {
// we're missing the additional processing of a schedule message to the responding attendee's scheduling inbox
return;
}

// We only care about request. reply and cancel are properly handled
// by $this->process already
if (strcasecmp($iTipMessage->method, 'REQUEST') !== 0) {
return;
}

// If $this->process set scheduleStatus to 1.2,
// it means that it was successfully delivered locally.
// Meaning that the ACL plugin is loaded and that a principial
// exists for the given recipient id, no need to double check
$this->processResources($iTipMessage);
}

/**
* @param string $uri
*/
Expand Down Expand Up @@ -554,4 +567,155 @@ private function stripOffMailTo(string $email): string {

return $email;
}

private function process(ITip\Message $iTipMessage) {
/** @var DavAclPlugin $aclPlugin */
$aclPlugin = $this->server->getPlugin('acl');

// Local delivery is not available if the ACL plugin is not loaded.
//
if (!$aclPlugin) {
return;
}

$caldavNS = '{'.self::NS_CALDAV.'}';

//before all the exciting stuff happens, the attendee needs to be updated.

// look for the organizer principal
$principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient);
if (!$principalUri) {
$iTipMessage->scheduleStatus = '3.7;Could not find principal.';

return;
}

// We found a principal URL, now we need to find its inbox.
// Unfortunately we may not have sufficient privileges to find this, so
// we are temporarily turning off ACL to let this come through.
//
// Once we support PHP 5.5, this should be wrapped in a try..finally
// block so we can ensure that this privilege gets added again after.
$this->server->removeListener('propFind', [$aclPlugin, 'propFind']);

$result = $this->server->getProperties(
$principalUri,
[
'{DAV:}principal-URL',
$caldavNS.'calendar-home-set',
$caldavNS.'schedule-inbox-URL',
$caldavNS.'schedule-default-calendar-URL',
'{http://sabredav.org/ns}email-address',
]
);

// Re-registering the ACL event
$this->server->on('propFind', [$aclPlugin, 'propFind'], 20);

if (!isset($result[$caldavNS.'schedule-inbox-URL'])) {
$iTipMessage->scheduleStatus = '5.2;Could not find local inbox';

return;
}
if (!isset($result[$caldavNS.'calendar-home-set'])) {
$iTipMessage->scheduleStatus = '5.2;Could not locate a calendar-home-set';

return;
}
if (!isset($result[$caldavNS.'schedule-default-calendar-URL'])) {
$iTipMessage->scheduleStatus = '5.2;Could not find a schedule-default-calendar-URL property';

return;
}

$calendarPath = $result[$caldavNS.'schedule-default-calendar-URL']->getHref();
$homePath = $result[$caldavNS.'calendar-home-set']->getHref();
$inboxPath = $result[$caldavNS.'schedule-inbox-URL']->getHref();

if ('REPLY' === $iTipMessage->method) {
$privilege = 'schedule-deliver-reply';
} else {
$privilege = 'schedule-deliver-invite';
}

if (!$aclPlugin->checkPrivileges($inboxPath, $caldavNS.$privilege, \Sabre\DAVACL\Plugin::R_PARENT, false)) {
$iTipMessage->scheduleStatus = '3.8;insufficient privileges: '.$privilege.' is required on the recipient schedule inbox.';
return;
}

// Next, we're going to find out if the item already exits in one of
// the users' calendars.
$uid = $iTipMessage->uid;

$newFileName = 'sabredav-'.\Sabre\DAV\UUIDUtil::getUUID().'.ics';

/** @var CalendarHome $home */
$home = $this->server->tree->getNodeForPath($homePath);
/** @var Inbox $inbox */
$inbox = $this->server->tree->getNodeForPath($inboxPath);

$currentObject = null;
$objectNode = null;
$oldICalendarData = null;
$isNewNode = false;

$result = $home->getCalendarObjectByUID($uid);
if ($result) {
// There was an existing object, we need to update probably.
$objectPath = $homePath.'/'.$result;
/** @var CalendarObject $objectNode */
$objectNode = $this->server->tree->getNodeForPath($objectPath);
$oldICalendarData = $objectNode->get();
$currentObject = Reader::read($oldICalendarData);
} else {
$isNewNode = true;
}

$broker = new ITip\Broker();
$newObject = $broker->processMessage($iTipMessage, $currentObject);

// create a new ics file for organizer with the new attendee status
$inbox->createFile($newFileName, $iTipMessage->message->serialize());

if (!$newObject) {
// We received an iTip message referring to a UID that we don't
// have in any calendars yet, and processMessage did not give us a
// calendarobject back.
//
// The implication is that processMessage did not understand the
// iTip message.
$iTipMessage->scheduleStatus = '5.0;iTip message was not processed by the server, likely because we didn\'t understand it.';

return;
}

// Note that we are bypassing ACL on purpose by calling this directly.
// We may need to look a bit deeper into this later. Supporting ACL
// here would be nice.
if ($isNewNode) {
/** @var Calendar $calendar */
$calendar = $this->server->tree->getNodeForPath($calendarPath);
/** @var resource $resource */
$resource = $newObject->serialize();
$calendar->createFile($newFileName, $resource);
} else {
// If the message was a reply, we may have to inform other
// attendees of this attendees status. Therefore we're shooting off
// another itipMessage.
if ('REPLY' === $iTipMessage->method) {
$this->processICalendarChange(
$oldICalendarData,
$newObject,
[$iTipMessage->recipient],
// this used to have the sender in the ignore field
// removed that because creating a scheduling update for the
// attendee would mean duplicating all this code
// this is not RFC6638 conform (See Section 4.2), but it's working
[]
miaulalala marked this conversation as resolved.
Show resolved Hide resolved
);
}
$objectNode->put($newObject->serialize());
}
$iTipMessage->scheduleStatus = '1.2;Message delivered locally';
}
}