|
31 | 31 | use DateTimeZone; |
32 | 32 | use OCA\DAV\CalDAV\CalDavBackend; |
33 | 33 | use OCA\DAV\CalDAV\CalendarHome; |
| 34 | +use OCA\DAV\Connector\Sabre\DavAclPlugin; |
34 | 35 | use OCP\IConfig; |
| 36 | +use Sabre\CalDAV\Calendar; |
| 37 | +use Sabre\CalDAV\CalendarObject; |
35 | 38 | use Sabre\CalDAV\ICalendar; |
| 39 | +use Sabre\CalDAV\Schedule\Inbox; |
36 | 40 | use Sabre\DAV\INode; |
37 | 41 | use Sabre\DAV\IProperties; |
38 | 42 | use Sabre\DAV\PropFind; |
@@ -117,59 +121,9 @@ public function propFind(PropFind $propFind, INode $node) { |
117 | 121 | } |
118 | 122 |
|
119 | 123 | /** |
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 |
154 | 125 | */ |
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 { |
173 | 127 | /** @var \Sabre\DAVACL\Plugin $aclPlugin */ |
174 | 128 | $aclPlugin = $this->server->getPlugin('acl'); |
175 | 129 | $principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient); |
@@ -202,6 +156,7 @@ public function scheduleLocalDelivery(ITip\Message $iTipMessage):void { |
202 | 156 | $vevent = $vcalendar->VEVENT; |
203 | 157 |
|
204 | 158 | // We don't support autoresponses for recurrencing events for now |
| 159 | + // @todo does this need to be fixed for resource booking? |
205 | 160 | if (isset($vevent->RRULE) || isset($vevent->RDATE)) { |
206 | 161 | return; |
207 | 162 | } |
@@ -257,6 +212,64 @@ public function scheduleLocalDelivery(ITip\Message $iTipMessage):void { |
257 | 212 | $this->schedulingResponses[] = $responseITipMessage; |
258 | 213 | } |
259 | 214 |
|
| 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 | + |
260 | 273 | /** |
261 | 274 | * @param string $uri |
262 | 275 | */ |
@@ -554,4 +567,155 @@ private function stripOffMailTo(string $email): string { |
554 | 567 |
|
555 | 568 | return $email; |
556 | 569 | } |
| 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 | + } |
557 | 721 | } |
0 commit comments