aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Calendar/Manager.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Calendar/Manager.php')
-rw-r--r--lib/private/Calendar/Manager.php153
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;
+ }
+ }
}