From 596c10c3a0cbf5bd4fa0d52a2d41c20ea99de679 Mon Sep 17 00:00:00 2001 From: Paul Mehrer Date: Wed, 2 Dec 2020 15:31:42 +0100 Subject: [PATCH] fix(Calendar CalDAV) create/put event refactor re move / displaycontainer Change-Id: Ib162226375b286c4fff5dabdbfd585e462f5c1d5 Reviewed-on: http://gerrit.tine20.com/customers/18671 Tested-by: Jenkins CI (http://ci.tine20.com/) Reviewed-by: Paul Mehrer --- .../Calendar/Frontend/WebDAV/EventTest.php | 7 + tine20/Calendar/Controller/Event.php | 21 +-- tine20/Calendar/Frontend/WebDAV/Event.php | 130 +++++++++++------- tine20/Calendar/Model/Attender.php | 3 +- 4 files changed, 103 insertions(+), 58 deletions(-) diff --git a/tests/tine20/Calendar/Frontend/WebDAV/EventTest.php b/tests/tine20/Calendar/Frontend/WebDAV/EventTest.php index 3827e973cc7..b771d881ec2 100644 --- a/tests/tine20/Calendar/Frontend/WebDAV/EventTest.php +++ b/tests/tine20/Calendar/Frontend/WebDAV/EventTest.php @@ -18,6 +18,8 @@ class Calendar_Frontend_WebDAV_EventTest extends Calendar_TestCase */ protected $objects = array(); + protected $_ifMatchHeader; + /** * Sets up the fixture. * This method is called before a test is executed. @@ -53,6 +55,9 @@ public function setUp() $_SERVER['HTTP_USER_AGENT'] = 'CalendarStore/5.0 (1127); iCal/5.0 (1535); Mac OS X/10.7.1 (11B26)'; $_SERVER['REQUEST_URI'] = 'lars'; + + $this->_ifMatchHeader = new Zend\Http\Header\IfMatch('shalala'); + Tinebase_Core::getRequest()->getHeaders()->addHeader($this->_ifMatchHeader); } public function tearDown() @@ -60,6 +65,7 @@ public function tearDown() parent::tearDown(); Tinebase_Core::getPreference('Calendar')->resetAppPrefsCache(); Tinebase_Core::set(Tinebase_Core::PREFERENCES, null); + Tinebase_Core::getRequest()->getHeaders()->removeHeader($this->_ifMatchHeader); } /** @@ -1012,6 +1018,7 @@ public function testMoveOriginEvent() // move event (origin container) Calendar_Frontend_WebDAV_Event::create($this->objects['initialContainer'], "$id.ics", stream_get_contents($event->get())); $oldEvent = new Calendar_Frontend_WebDAV_Event($this->objects['sharedContainer'], "$id.ics"); + $oldEvent->delete(); $loadedEvent = new Calendar_Frontend_WebDAV_Event($this->objects['initialContainer'], "$id.ics"); diff --git a/tine20/Calendar/Controller/Event.php b/tine20/Calendar/Controller/Event.php index 8027ef53f2a..4500b2b3fae 100644 --- a/tine20/Calendar/Controller/Event.php +++ b/tine20/Calendar/Controller/Event.php @@ -2762,15 +2762,15 @@ protected function _createAttender(Calendar_Model_Attender $attender, Calendar_M // attach to display calendar if attender has/is a useraccount if ($userAccountId) { - if ($calendar->type == Tinebase_Model_Container::TYPE_PERSONAL && Tinebase_Container::getInstance()->hasGrant($userAccountId, $calendar, Tinebase_Model_Grants::GRANT_ADMIN)) { + if ($calendar->type === Tinebase_Model_Container::TYPE_PERSONAL && Tinebase_Container::getInstance()->hasGrant($userAccountId, $calendar, Tinebase_Model_Grants::GRANT_ADMIN)) { // if attender has admin grant to (is owner of) personal physical container, this phys. cal also gets displ. cal $attender->displaycontainer_id = $calendar->getId(); - } else if ($attender->displaycontainer_id && $userAccountId == Tinebase_Core::getUser()->getId() && Tinebase_Container::getInstance()->hasGrant($userAccountId, $attender->displaycontainer_id, Tinebase_Model_Grants::GRANT_ADMIN)) { - // allow user to set his own displ. cal - $attender->displaycontainer_id = $attender->displaycontainer_id; - } else { - $displayCalId = self::getDefaultDisplayContainerId($userAccountId); - $attender->displaycontainer_id = $displayCalId; + + // allow user to set his own *personal* displ. cal + // otherwise set default display container + } elseif (!$attender->displaycontainer_id || $userAccountId !== Tinebase_Core::getUser()->getId() || !Tinebase_Container::getInstance()->hasGrant($userAccountId, $attender->displaycontainer_id, Tinebase_Model_Grants::GRANT_ADMIN) || + Tinebase_Container::getInstance()->get($attender->displaycontainer_id)->type !== Tinebase_Model_Container::TYPE_PERSONAL) { + $attender->displaycontainer_id = self::getDefaultDisplayContainerId($userAccountId); } } else if ($attender->user_type === Calendar_Model_Attender::USERTYPE_RESOURCE) { @@ -2884,11 +2884,12 @@ protected function _updateAttender($attender, $currentAttender, $event, $isResch // update display calendar if attender has/is a useraccount if ($userAccountId) { - if ($calendar->type == Tinebase_Model_Container::TYPE_PERSONAL && Tinebase_Container::getInstance()->hasGrant($userAccountId, $calendar, Tinebase_Model_Grants::GRANT_ADMIN)) { + if ($calendar->type === Tinebase_Model_Container::TYPE_PERSONAL && Tinebase_Container::getInstance()->hasGrant($userAccountId, $calendar, Tinebase_Model_Grants::GRANT_ADMIN)) { // if attender has admin grant to personal physical container, this phys. cal also gets displ. cal $attender->displaycontainer_id = $calendar->getId(); - } else if ($userAccountId == Tinebase_Core::getUser()->getId() && Tinebase_Container::getInstance()->hasGrant($userAccountId, $attender->displaycontainer_id, Tinebase_Model_Grants::GRANT_ADMIN)) { - // allow user to set his own displ. cal + } else if ($userAccountId === Tinebase_Core::getUser()->getId() && Tinebase_Container::getInstance()->hasGrant($userAccountId, $attender->displaycontainer_id, Tinebase_Model_Grants::GRANT_ADMIN) && + Tinebase_Container::getInstance()->get($attender->displaycontainer_id)->type === Tinebase_Model_Container::TYPE_PERSONAL) { + // allow user to set his own *personal* displ. cal $attender->displaycontainer_id = $attender->displaycontainer_id; } else { $attender->displaycontainer_id = $currentAttender->displaycontainer_id; diff --git a/tine20/Calendar/Frontend/WebDAV/Event.php b/tine20/Calendar/Frontend/WebDAV/Event.php index b8f51f9c2ce..920e7ea5496 100644 --- a/tine20/Calendar/Frontend/WebDAV/Event.php +++ b/tine20/Calendar/Frontend/WebDAV/Event.php @@ -234,27 +234,12 @@ public static function create(Tinebase_Model_Container $container, $name, $vobje Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' update existing event'); $vevent = new self($container, $existingEvent); - /** @var Calendar_Model_Event $existingEvent */ - $existingEvent = clone $existingEvent; - $existingEvent->alarms = $event->alarms; - $existingEvent->transp = $event->transp; - if (null === ($contactId = $container->getOwner())) { - $contactId = Tinebase_Core::getUser()->contact_id; - } else { - $contactId = Tinebase_User::getInstance()->getUserById($contactId)->contact_id; - } - if (null !== ($attender = $event->attendee->find('user_id', $contactId))) { - if (null !== ($oldAttender = $existingEvent->attendee->find('user_id', $contactId))) { - $existingEvent->attendee->removeRecord($oldAttender); - $attender->setId($oldAttender->getId()); - } - $existingEvent->attendee->addRecord($attender); - } + $event = static::_allowOnlyAttendeeProperties($existingEvent, $event, $container); $calCtrl = Calendar_Controller_Event::getInstance(); $oldCalenderAcl = $calCtrl->doContainerACLChecks(); try { - if ($existingEvent->hasExternalOrganizer()) { + if ($event->hasExternalOrganizer()) { $calCtrl->doContainerACLChecks(false); } $vobject = Calendar_Convert_Event_VCalendar_Abstract::getVObject($vobjectData); @@ -268,10 +253,14 @@ public static function create(Tinebase_Model_Container $container, $name, $vobje break; } } - $vcalendar = $converter->fromTine20Model($existingEvent); + $vcalendar = $converter->fromTine20Model($event); if (null !== $xTine20Container) { static::_addXPropsToVEvent($vcalendar, $xTine20Container); } + // set a dummy if-match header + if (!Tinebase_Core::getRequest()->getHeaders()->has('If-Match')) { + Tinebase_Core::getRequest()->getHeaders()->addHeader(new Zend\Http\Header\IfMatch('dummy')); + } $vevent->put($vcalendar->serialize()); } finally { $calCtrl->doContainerACLChecks($oldCalenderAcl); @@ -281,6 +270,28 @@ public static function create(Tinebase_Model_Container $container, $name, $vobje return $vevent; } + protected static function _allowOnlyAttendeeProperties(Calendar_Model_Event $_origEvent, Calendar_Model_Event $_event, Tinebase_Model_Container $_container) + { + $event = clone $_origEvent; + $event->alarms = $_event->alarms; + $event->transp = $_event->transp; + if (null === ($contactId = $_container->getOwner())) { + $contactId = Tinebase_Core::getUser()->contact_id; + } else { + $contactId = Tinebase_User::getInstance()->getUserById($contactId)->contact_id; + } + /** @var Calendar_Model_Attender $attender */ + if (null !== ($attender = $_event->attendee->find('user_id', $contactId))) { + if (null !== ($oldAttender = $event->attendee->find('user_id', $contactId))) { + $event->attendee->removeRecord($oldAttender); + $attender->setId($oldAttender->getId()); + } + $event->attendee->addRecord($attender); + } + + return $event; + } + /** * @param Calendar_Convert_Event_VCalendar_Abstract $converter * @throws \Sabre\DAV\Exception\Forbidden @@ -508,52 +519,77 @@ public function put($cardData, $retry = true) } // Converting to UTF-8, if needed $cardData = Sabre\DAV\StringUtil::ensureUTF8($cardData); - - #Sabre_CalDAV_ICalendarUtil::validateICalendarObject($cardData, array('VEVENT', 'VFREEBUSY')); - $vobject = Calendar_Convert_Event_VCalendar_Abstract::getVObject($cardData); + $xTine20Container = null; foreach ($vobject->children() as $component) { if (isset($component->{'X-TINE20-CONTAINER'})) { - $xTine20Container = $component->{'X-TINE20-CONTAINER'}; + $xTine20Container = $component->{'X-TINE20-CONTAINER'}->getValue(); break; } } - - // concurrency management is based on etag in CalDAV - $event = $this->_getConverter()->toTine20Model($vobject, $this->getRecord(), array( + + // clone, otherwise $this->_event and $event would be the same object + $event = $this->_getConverter()->toTine20Model($vobject, clone $this->getRecord(), array( Calendar_Convert_Event_VCalendar_Abstract::OPTION_USE_SERVER_MODLOG => true, )); if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " " . print_r($event->toArray(), true)); - $currentEvent = $this->getRecord(); + if (null !== $xTine20Container && $currentEvent->container_id !== $xTine20Container && + $currentEvent->attendee->find('displaycontainer_id', $xTine20Container) === null) { + $xTine20Container = null; + } + $inDisplayContainer = $currentEvent->attendee->find('displaycontainer_id', $this->_container->getId()) !== null; + $currentOwnAttendee = Calendar_Model_Attender::getOwnAttender($currentEvent->attendee); + $xTine20ContainerAttendee = $xTine20Container ? + $currentEvent->attendee->find('displaycontainer_id', $xTine20Container) : null; $currentContainer = Tinebase_Container::getInstance()->getContainerById($currentEvent->container_id); - - // client sends CalDAV event -> handle a container move - if (isset($xTine20Container)) { - if ($xTine20Container->getValue() == $currentContainer->getId()) { + $currentContainer->resolveGrantsAndPath(); + + // no If-Match header -> no moves / displaycontainer changes + if (!Tinebase_Core::getRequest()->getHeaders()->has('If-Match')) { + $event = static::_allowOnlyAttendeeProperties($currentEvent, $event, $this->_container); + + // no xTineContainer + } elseif (null === $xTine20Container) { + // target container is not a current displaycontainer and we have admin on origin container or are organizer + // move to target container + if (!$inDisplayContainer && ($currentContainer->account_grants->{Tinebase_Model_Grants::GRANT_ADMIN} || + $currentEvent->organizer === Tinebase_Core::getUser()->contact_id || + $currentEvent->organizer === Calendar_Controller_MSEventFacade::getInstance()->getCalendarUser()->user_id)) { + $event->container_id = $this->_container->getId(); + // target container is not a current displaycontainer and is not the origin container ( and we are not organizer or admin on origin) + // set as new displaycontainer for OUR attendee (not currentCalendarUsers attendee!) + } elseif (!$inDisplayContainer && $currentOwnAttendee && $event->container_id !== $this->_container->getId()) { + Calendar_Controller_MSEventFacade::getInstance()->setDisplaycontainer($event, $this->_container->getId(), + $currentOwnAttendee); + } // else do nothing + + } else { // we have a xTineContainer + // the xTineContainer is the origin container => do the move + if ($event->container_id === $xTine20Container) { + // grant check? + /* ($currentContainer->account_grants->{Tinebase_Model_Grants::GRANT_ADMIN} || + $currentEvent->organizer === Calendar_Controller_MSEventFacade::getInstance()->getCalendarUser()->user_id)) { +*/ $event->container_id = $this->_container->getId(); - } else { - // @TODO allow organizer to move original cal when he edits the displaycal event? - if ($this->_container->type == Tinebase_Model_Container::TYPE_PERSONAL) { - Calendar_Controller_MSEventFacade::getInstance()->setDisplaycontainer($event, $this->_container->getId()); - } - } - } - // event was created by current user -> allow container move - else if ($currentEvent->created_by == Tinebase_Core::getUser()->getId()) { - $event->container_id = $this->_container->getId(); - } + // WE (not the currentCalendarUser) are an attendee and xTineContainer was our displaycontainer + // => change our displaycontainer + } elseif ($currentOwnAttendee && $currentOwnAttendee->displaycontainer_id === $xTine20Container) { + if ($xTine20Container !== $this->_container->getId()) { + Calendar_Controller_MSEventFacade::getInstance()->setDisplaycontainer($event, + $this->_container->getId(), $currentOwnAttendee); + } - // client sends event from iMIP invitation -> only allow displaycontainer move - else { - if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) - Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " X-TINE20-CONTAINER not present -> restrict container moves"); - if ($this->_container->type == Tinebase_Model_Container::TYPE_PERSONAL) { - Calendar_Controller_MSEventFacade::getInstance()->setDisplaycontainer($event, $this->_container->getId()); + // problem here is: + // secretary has admin on boss calendar, boss forwards imip to secretary with secretary being an attendee too + // now what?! + } elseif ($xTine20ContainerAttendee && $xTine20Container !== $this->_container->getId()) { + Calendar_Controller_MSEventFacade::getInstance()->setDisplaycontainer($event, + $this->_container->getId(), $xTine20ContainerAttendee); } } diff --git a/tine20/Calendar/Model/Attender.php b/tine20/Calendar/Model/Attender.php index 3ed523c25e9..7f0c73ed3c3 100644 --- a/tine20/Calendar/Model/Attender.php +++ b/tine20/Calendar/Model/Attender.php @@ -3,7 +3,7 @@ * @package Calendar * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3 * @author Cornelius Weiss - * @copyright Copyright (c) 2009-2018 Metaways Infosystems GmbH (http://www.metaways.de) + * @copyright Copyright (c) 2009-2020 Metaways Infosystems GmbH (http://www.metaways.de) */ /** @@ -17,6 +17,7 @@ * @property string status * @property string status_authkey * @property string user_type + * @property string displaycontainer_id */ class Calendar_Model_Attender extends Tinebase_Record_Abstract {