Skip to content

Commit 94c80aa

Browse files
Merge pull request #52622 from nextcloud/fix/refactor-imip
fix: aliases and capitalization of emails
2 parents 1cb6dc0 + 7e92b15 commit 94c80aa

File tree

6 files changed

+388
-1558
lines changed

6 files changed

+388
-1558
lines changed

apps/dav/lib/CalDAV/CalendarImpl.php

Lines changed: 85 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
2525
use Sabre\DAV\Exception\Conflict;
2626
use Sabre\VObject\Component\VCalendar;
27-
use Sabre\VObject\Component\VEvent;
2827
use Sabre\VObject\Component\VTimeZone;
2928
use Sabre\VObject\ITip\Message;
29+
use Sabre\VObject\ParseException;
3030
use Sabre\VObject\Property;
3131
use Sabre\VObject\Reader;
3232

@@ -41,6 +41,9 @@ public function __construct(
4141
) {
4242
}
4343

44+
private const DAV_PROPERTY_USER_ADDRESS = '{http://sabredav.org/ns}email-address';
45+
private const DAV_PROPERTY_USER_ADDRESSES = '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set';
46+
4447
/**
4548
* @return string defining the technical unique key
4649
* @since 13.0.0
@@ -219,58 +222,93 @@ public function createFromStringMinimal(string $name, string $calendarData): voi
219222
* @throws CalendarException
220223
*/
221224
public function handleIMipMessage(string $name, string $calendarData): void {
222-
$server = $this->getInvitationResponseServer();
223-
224-
/** @var CustomPrincipalPlugin $plugin */
225-
$plugin = $server->getServer()->getPlugin('auth');
226-
// we're working around the previous implementation
227-
// that only allowed the public system principal to be used
228-
// so set the custom principal here
229-
$plugin->setCurrentPrincipal($this->calendar->getPrincipalURI());
230225

231-
if (empty($this->calendarInfo['uri'])) {
232-
throw new CalendarException('Could not write to calendar as URI parameter is missing');
226+
try {
227+
/** @var VCalendar $vObject|null */
228+
$vObject = Reader::read($calendarData);
229+
} catch (ParseException $e) {
230+
throw new CalendarException('iMip message could not be processed because an error occurred while parsing the iMip message', 0, $e);
233231
}
234-
// Force calendar change URI
235-
/** @var Schedule\Plugin $schedulingPlugin */
236-
$schedulingPlugin = $server->getServer()->getPlugin('caldav-schedule');
237-
// Let sabre handle the rest
238-
$iTipMessage = new Message();
239-
/** @var VCalendar $vObject */
240-
$vObject = Reader::read($calendarData);
241-
/** @var VEvent $vEvent */
242-
$vEvent = $vObject->{'VEVENT'};
243-
244-
if ($vObject->{'METHOD'} === null) {
245-
throw new CalendarException('No Method provided for scheduling data. Could not process message');
232+
// validate the iMip message
233+
if (!isset($vObject->METHOD)) {
234+
throw new CalendarException('iMip message contains no valid method');
246235
}
247-
248-
if (!isset($vEvent->{'ORGANIZER'}) || !isset($vEvent->{'ATTENDEE'})) {
249-
throw new CalendarException('Could not process scheduling data, neccessary data missing from ICAL');
236+
if (!isset($vObject->VEVENT)) {
237+
throw new CalendarException('iMip message contains no event');
238+
}
239+
if (!isset($vObject->VEVENT->UID)) {
240+
throw new CalendarException('iMip message event dose not contain a UID');
250241
}
251-
$organizer = $vEvent->{'ORGANIZER'}->getValue();
252-
$attendee = $vEvent->{'ATTENDEE'}->getValue();
253-
254-
$iTipMessage->method = $vObject->{'METHOD'}->getValue();
255-
if ($iTipMessage->method === 'REQUEST') {
256-
$iTipMessage->sender = $organizer;
257-
$iTipMessage->recipient = $attendee;
258-
} elseif ($iTipMessage->method === 'REPLY') {
259-
if ($server->isExternalAttendee($vEvent->{'ATTENDEE'}->getValue())) {
260-
$iTipMessage->recipient = $organizer;
261-
} else {
262-
$iTipMessage->recipient = $attendee;
242+
if (!isset($vObject->VEVENT->ORGANIZER)) {
243+
throw new CalendarException('iMip message event dose not contain an organizer');
244+
}
245+
if (!isset($vObject->VEVENT->ATTENDEE)) {
246+
throw new CalendarException('iMip message event dose not contain an attendee');
247+
}
248+
if (empty($this->calendarInfo['uri'])) {
249+
throw new CalendarException('Could not write to calendar as URI parameter is missing');
250+
}
251+
// construct dav server
252+
$server = $this->getInvitationResponseServer();
253+
/** @var CustomPrincipalPlugin $authPlugin */
254+
$authPlugin = $server->getServer()->getPlugin('auth');
255+
// we're working around the previous implementation
256+
// that only allowed the public system principal to be used
257+
// so set the custom principal here
258+
$authPlugin->setCurrentPrincipal($this->calendar->getPrincipalURI());
259+
// retrieve all users addresses
260+
$userProperties = $server->getServer()->getProperties($this->calendar->getPrincipalURI(), [ self::DAV_PROPERTY_USER_ADDRESS, self::DAV_PROPERTY_USER_ADDRESSES ]);
261+
$userAddress = 'mailto:' . ($userProperties[self::DAV_PROPERTY_USER_ADDRESS] ?? null);
262+
$userAddresses = $userProperties[self::DAV_PROPERTY_USER_ADDRESSES]->getHrefs() ?? [];
263+
$userAddresses = array_map('strtolower', array_map('urldecode', $userAddresses));
264+
// validate the method, recipient and sender
265+
$imipMethod = strtoupper($vObject->METHOD->getValue());
266+
if (in_array($imipMethod, ['REPLY', 'REFRESH'], true)) {
267+
// extract sender (REPLY and REFRESH method should only have one attendee)
268+
$sender = strtolower($vObject->VEVENT->ATTENDEE->getValue());
269+
// extract and verify the recipient
270+
$recipient = strtolower($vObject->VEVENT->ORGANIZER->getValue());
271+
if (!in_array($recipient, $userAddresses, true)) {
272+
throw new CalendarException('iMip message dose not contain an organizer that matches the user');
273+
}
274+
// if the recipient address is not the same as the user address this means an alias was used
275+
// the iTip broker uses the users primary email address during processing
276+
if ($userAddress !== $recipient) {
277+
$recipient = $userAddress;
278+
}
279+
} elseif (in_array($imipMethod, ['PUBLISH', 'REQUEST', 'ADD', 'CANCEL'], true)) {
280+
// extract sender
281+
$sender = strtolower($vObject->VEVENT->ORGANIZER->getValue());
282+
// extract and verify the recipient
283+
foreach ($vObject->VEVENT->ATTENDEE as $attendee) {
284+
$recipient = strtolower($attendee->getValue());
285+
if (in_array($recipient, $userAddresses, true)) {
286+
break;
287+
}
288+
$recipient = null;
289+
}
290+
if ($recipient === null) {
291+
throw new CalendarException('iMip message dose not contain an attendee that matches the user');
292+
}
293+
// if the recipient address is not the same as the user address this means an alias was used
294+
// the iTip broker uses the users primary email address during processing
295+
if ($userAddress !== $recipient) {
296+
$recipient = $userAddress;
263297
}
264-
$iTipMessage->sender = $attendee;
265-
} elseif ($iTipMessage->method === 'CANCEL') {
266-
$iTipMessage->recipient = $attendee;
267-
$iTipMessage->sender = $organizer;
298+
} else {
299+
throw new CalendarException('iMip message contains a method that is not supported: ' . $imipMethod);
268300
}
269-
$iTipMessage->uid = isset($vEvent->{'UID'}) ? $vEvent->{'UID'}->getValue() : '';
270-
$iTipMessage->component = 'VEVENT';
271-
$iTipMessage->sequence = isset($vEvent->{'SEQUENCE'}) ? (int)$vEvent->{'SEQUENCE'}->getValue() : 0;
272-
$iTipMessage->message = $vObject;
273-
$server->server->emit('schedule', [$iTipMessage]);
301+
// generate the iTip message
302+
$iTip = new Message();
303+
$iTip->method = $imipMethod;
304+
$iTip->sender = $sender;
305+
$iTip->recipient = $recipient;
306+
$iTip->component = 'VEVENT';
307+
$iTip->uid = $vObject->VEVENT->UID->getValue();
308+
$iTip->sequence = isset($vObject->VEVENT->SEQUENCE) ? (int)$vObject->VEVENT->SEQUENCE->getValue() : 1;
309+
$iTip->message = $vObject;
310+
311+
$server->server->emit('schedule', [$iTip]);
274312
}
275313

276314
public function getInvitationResponseServer(): InvitationResponseServer {

apps/dav/lib/CalDAV/Schedule/Plugin.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ public function propFind(PropFind $propFind, INode $node) {
132132
* @param string $principal
133133
* @return array
134134
*/
135-
protected function getAddressesForPrincipal($principal) {
135+
public function getAddressesForPrincipal($principal) {
136136
$result = parent::getAddressesForPrincipal($principal);
137137

138138
if ($result === null) {

0 commit comments

Comments
 (0)