diff options
Diffstat (limited to 'lib/private/Calendar/Manager.php')
-rw-r--r-- | lib/private/Calendar/Manager.php | 153 |
1 files changed, 151 insertions, 2 deletions
diff --git a/lib/private/Calendar/Manager.php b/lib/private/Calendar/Manager.php index 16e142264aa..f0b8e9fd50d 100644 --- a/lib/private/Calendar/Manager.php +++ b/lib/private/Calendar/Manager.php @@ -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; + } + } } |