2020use Sabre \CalDAV \Xml \Property \ScheduleCalendarTransp ;
2121use Sabre \DAV \Exception \Conflict ;
2222use Sabre \VObject \Component \VCalendar ;
23- use Sabre \VObject \Component \VEvent ;
2423use Sabre \VObject \Component \VTimeZone ;
2524use Sabre \VObject \ITip \Message ;
25+ use Sabre \VObject \ParseException ;
2626use Sabre \VObject \Property ;
2727use Sabre \VObject \Reader ;
2828use function Sabre \Uri \split as uriSplit ;
@@ -36,6 +36,9 @@ public function __construct(
3636 ) {
3737 }
3838
39+ private const DAV_PROPERTY_USER_ADDRESS = '{http://sabredav.org/ns}email-address ' ;
40+ private const DAV_PROPERTY_USER_ADDRESSES = '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set ' ;
41+
3942 /**
4043 * @return string defining the technical unique key
4144 * @since 13.0.0
@@ -210,58 +213,93 @@ public function createFromString(string $name, string $calendarData): void {
210213 * @throws CalendarException
211214 */
212215 public function handleIMipMessage (string $ name , string $ calendarData ): void {
213- $ server = $ this ->getInvitationResponseServer ();
214-
215- /** @var CustomPrincipalPlugin $plugin */
216- $ plugin = $ server ->getServer ()->getPlugin ('auth ' );
217- // we're working around the previous implementation
218- // that only allowed the public system principal to be used
219- // so set the custom principal here
220- $ plugin ->setCurrentPrincipal ($ this ->calendar ->getPrincipalURI ());
221216
222- if (empty ($ this ->calendarInfo ['uri ' ])) {
223- throw new CalendarException ('Could not write to calendar as URI parameter is missing ' );
217+ try {
218+ /** @var VCalendar $vObject|null */
219+ $ vObject = Reader::read ($ calendarData );
220+ } catch (ParseException $ e ) {
221+ throw new CalendarException ('iMip message could not be processed because an error occurred while parsing the iMip message ' , 0 , $ e );
224222 }
225- // Force calendar change URI
226- /** @var Schedule\Plugin $schedulingPlugin */
227- $ schedulingPlugin = $ server ->getServer ()->getPlugin ('caldav-schedule ' );
228- // Let sabre handle the rest
229- $ iTipMessage = new Message ();
230- /** @var VCalendar $vObject */
231- $ vObject = Reader::read ($ calendarData );
232- /** @var VEvent $vEvent */
233- $ vEvent = $ vObject ->{'VEVENT ' };
234-
235- if ($ vObject ->{'METHOD ' } === null ) {
236- throw new CalendarException ('No Method provided for scheduling data. Could not process message ' );
223+ // validate the iMip message
224+ if (!isset ($ vObject ->METHOD )) {
225+ throw new CalendarException ('iMip message contains no valid method ' );
237226 }
238-
239- if (!isset ($ vEvent ->{'ORGANIZER ' }) || !isset ($ vEvent ->{'ATTENDEE ' })) {
240- throw new CalendarException ('Could not process scheduling data, neccessary data missing from ICAL ' );
227+ if (!isset ($ vObject ->VEVENT )) {
228+ throw new CalendarException ('iMip message contains no event ' );
241229 }
242- $ organizer = $ vEvent ->{'ORGANIZER ' }->getValue ();
243- $ attendee = $ vEvent ->{'ATTENDEE ' }->getValue ();
244-
245- $ iTipMessage ->method = $ vObject ->{'METHOD ' }->getValue ();
246- if ($ iTipMessage ->method === 'REQUEST ' ) {
247- $ iTipMessage ->sender = $ organizer ;
248- $ iTipMessage ->recipient = $ attendee ;
249- } elseif ($ iTipMessage ->method === 'REPLY ' ) {
250- if ($ server ->isExternalAttendee ($ vEvent ->{'ATTENDEE ' }->getValue ())) {
251- $ iTipMessage ->recipient = $ organizer ;
252- } else {
253- $ iTipMessage ->recipient = $ attendee ;
230+ if (!isset ($ vObject ->VEVENT ->UID )) {
231+ throw new CalendarException ('iMip message event dose not contain a UID ' );
232+ }
233+ if (!isset ($ vObject ->VEVENT ->ORGANIZER )) {
234+ throw new CalendarException ('iMip message event dose not contain an organizer ' );
235+ }
236+ if (!isset ($ vObject ->VEVENT ->ATTENDEE )) {
237+ throw new CalendarException ('iMip message event dose not contain an attendee ' );
238+ }
239+ if (empty ($ this ->calendarInfo ['uri ' ])) {
240+ throw new CalendarException ('Could not write to calendar as URI parameter is missing ' );
241+ }
242+ // construct dav server
243+ $ server = $ this ->getInvitationResponseServer ();
244+ /** @var CustomPrincipalPlugin $authPlugin */
245+ $ authPlugin = $ server ->getServer ()->getPlugin ('auth ' );
246+ // we're working around the previous implementation
247+ // that only allowed the public system principal to be used
248+ // so set the custom principal here
249+ $ authPlugin ->setCurrentPrincipal ($ this ->calendar ->getPrincipalURI ());
250+ // retrieve all users addresses
251+ $ userProperties = $ server ->getServer ()->getProperties ($ this ->calendar ->getPrincipalURI (), [ self ::DAV_PROPERTY_USER_ADDRESS , self ::DAV_PROPERTY_USER_ADDRESSES ]);
252+ $ userAddress = 'mailto: ' . ($ userProperties [self ::DAV_PROPERTY_USER_ADDRESS ] ?? null );
253+ $ userAddresses = $ userProperties [self ::DAV_PROPERTY_USER_ADDRESSES ]->getHrefs () ?? [];
254+ $ userAddresses = array_map ('strtolower ' , array_map ('urldecode ' , $ userAddresses ));
255+ // validate the method, recipient and sender
256+ $ imipMethod = strtoupper ($ vObject ->METHOD ->getValue ());
257+ if (in_array ($ imipMethod , ['REPLY ' , 'REFRESH ' ], true )) {
258+ // extract sender (REPLY and REFRESH method should only have one attendee)
259+ $ sender = strtolower ($ vObject ->VEVENT ->ATTENDEE ->getValue ());
260+ // extract and verify the recipient
261+ $ recipient = strtolower ($ vObject ->VEVENT ->ORGANIZER ->getValue ());
262+ if (!in_array ($ recipient , $ userAddresses , true )) {
263+ throw new CalendarException ('iMip message dose not contain an organizer that matches the user ' );
264+ }
265+ // if the recipient address is not the same as the user address this means an alias was used
266+ // the iTip broker uses the users primary email address during processing
267+ if ($ userAddress !== $ recipient ) {
268+ $ recipient = $ userAddress ;
269+ }
270+ } elseif (in_array ($ imipMethod , ['PUBLISH ' , 'REQUEST ' , 'ADD ' , 'CANCEL ' ], true )) {
271+ // extract sender
272+ $ sender = strtolower ($ vObject ->VEVENT ->ORGANIZER ->getValue ());
273+ // extract and verify the recipient
274+ foreach ($ vObject ->VEVENT ->ATTENDEE as $ attendee ) {
275+ $ recipient = strtolower ($ attendee ->getValue ());
276+ if (in_array ($ recipient , $ userAddresses , true )) {
277+ break ;
278+ }
279+ $ recipient = null ;
280+ }
281+ if ($ recipient === null ) {
282+ throw new CalendarException ('iMip message dose not contain an attendee that matches the user ' );
254283 }
255- $ iTipMessage ->sender = $ attendee ;
256- } elseif ($ iTipMessage ->method === 'CANCEL ' ) {
257- $ iTipMessage ->recipient = $ attendee ;
258- $ iTipMessage ->sender = $ organizer ;
284+ // if the recipient address is not the same as the user address this means an alias was used
285+ // the iTip broker uses the users primary email address during processing
286+ if ($ userAddress !== $ recipient ) {
287+ $ recipient = $ userAddress ;
288+ }
289+ } else {
290+ throw new CalendarException ('iMip message contains a method that is not supported: ' . $ imipMethod );
259291 }
260- $ iTipMessage ->uid = isset ($ vEvent ->{'UID ' }) ? $ vEvent ->{'UID ' }->getValue () : '' ;
261- $ iTipMessage ->component = 'VEVENT ' ;
262- $ iTipMessage ->sequence = isset ($ vEvent ->{'SEQUENCE ' }) ? (int )$ vEvent ->{'SEQUENCE ' }->getValue () : 0 ;
263- $ iTipMessage ->message = $ vObject ;
264- $ server ->server ->emit ('schedule ' , [$ iTipMessage ]);
292+ // generate the iTip message
293+ $ iTip = new Message ();
294+ $ iTip ->method = $ imipMethod ;
295+ $ iTip ->sender = $ sender ;
296+ $ iTip ->recipient = $ recipient ;
297+ $ iTip ->component = 'VEVENT ' ;
298+ $ iTip ->uid = $ vObject ->VEVENT ->UID ->getValue ();
299+ $ iTip ->sequence = isset ($ vObject ->VEVENT ->SEQUENCE ) ? (int )$ vObject ->VEVENT ->SEQUENCE ->getValue () : 1 ;
300+ $ iTip ->message = $ vObject ;
301+
302+ $ server ->server ->emit ('schedule ' , [$ iTip ]);
265303 }
266304
267305 public function getInvitationResponseServer (): InvitationResponseServer {
0 commit comments