Skip to content

Commit 5079bf6

Browse files
authored
Merge pull request #33001 from nextcloud/enh/imip-invitations-responses
Support iMIP invitations from Mail
2 parents 4991448 + 4ca4b02 commit 5079bf6

9 files changed

Lines changed: 698 additions & 11 deletions

File tree

apps/dav/lib/CalDAV/CalDavBackend.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,6 +1864,10 @@ public function search(array $calendarInfo, $pattern, array $searchProperties,
18641864
}
18651865
}
18661866

1867+
if(isset($options['uid'])) {
1868+
$outerQuery->andWhere($outerQuery->expr()->eq('uid', $outerQuery->createNamedParameter($options['uid'])));
1869+
}
1870+
18671871
if (!empty($options['types'])) {
18681872
$or = $outerQuery->expr()->orX();
18691873
foreach ($options['types'] as $type) {

apps/dav/lib/CalDAV/CalendarHome.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ public function calendarSearch(array $filters, $limit = null, $offset = null) {
206206
return $this->caldavBackend->calendarSearch($principalUri, $filters, $limit, $offset);
207207
}
208208

209-
210209
public function enableCachedSubscriptionsForThisRequest() {
211210
$this->returnCachedSubscriptions = true;
212211
}

apps/dav/lib/CalDAV/CalendarImpl.php

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,19 @@
2929

3030
use OCA\DAV\CalDAV\Auth\CustomPrincipalPlugin;
3131
use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer;
32+
use OCP\AppFramework\Utility\ITimeFactory;
3233
use OCP\Calendar\Exceptions\CalendarException;
3334
use OCP\Calendar\ICreateFromString;
3435
use OCP\Constants;
36+
use OCP\Security\ISecureRandom;
37+
use Psr\Log\LoggerInterface;
3538
use Sabre\DAV\Exception\Conflict;
39+
use Sabre\VObject\Component\VCalendar;
40+
use Sabre\VObject\Component\VEvent;
41+
use Sabre\VObject\Document;
42+
use Sabre\VObject\ITip\Message;
43+
use Sabre\VObject\Property\VCard\DateTime;
44+
use Sabre\VObject\Reader;
3645
use function Sabre\Uri\split as uriSplit;
3746

3847
class CalendarImpl implements ICreateFromString {
@@ -46,13 +55,6 @@ class CalendarImpl implements ICreateFromString {
4655
/** @var array */
4756
private $calendarInfo;
4857

49-
/**
50-
* CalendarImpl constructor.
51-
*
52-
* @param Calendar $calendar
53-
* @param array $calendarInfo
54-
* @param CalDavBackend $backend
55-
*/
5658
public function __construct(Calendar $calendar,
5759
array $calendarInfo,
5860
CalDavBackend $backend) {
@@ -177,4 +179,59 @@ public function createFromString(string $name, string $calendarData): void {
177179
fclose($stream);
178180
}
179181
}
182+
183+
/**
184+
* @throws CalendarException
185+
*/
186+
public function handleIMipMessage(string $name, string $calendarData): void {
187+
$server = new InvitationResponseServer(false);
188+
189+
/** @var CustomPrincipalPlugin $plugin */
190+
$plugin = $server->server->getPlugin('auth');
191+
// we're working around the previous implementation
192+
// that only allowed the public system principal to be used
193+
// so set the custom principal here
194+
$plugin->setCurrentPrincipal($this->calendar->getPrincipalURI());
195+
196+
if (empty($this->calendarInfo['uri'])) {
197+
throw new CalendarException('Could not write to calendar as URI parameter is missing');
198+
}
199+
// Force calendar change URI
200+
/** @var Schedule\Plugin $schedulingPlugin */
201+
$schedulingPlugin = $server->server->getPlugin('caldav-schedule');
202+
// Let sabre handle the rest
203+
$iTipMessage = new Message();
204+
/** @var VCalendar $vObject */
205+
$vObject = Reader::read($calendarData);
206+
/** @var VEvent $vEvent */
207+
$vEvent = $vObject->{'VEVENT'};
208+
209+
if($vObject->{'METHOD'} === null) {
210+
throw new CalendarException('No Method provided for scheduling data. Could not process message');
211+
}
212+
213+
if(!isset($vEvent->{'ORGANIZER'}) || !isset($vEvent->{'ATTENDEE'})) {
214+
throw new CalendarException('Could not process scheduling data, neccessary data missing from ICAL');
215+
}
216+
$orgaizer = $vEvent->{'ORGANIZER'}->getValue();
217+
$attendee = $vEvent->{'ATTENDEE'}->getValue();
218+
219+
$iTipMessage->method = $vObject->{'METHOD'}->getValue();
220+
if($iTipMessage->method === 'REPLY') {
221+
if ($server->isExternalAttendee($vEvent->{'ATTENDEE'}->getValue())) {
222+
$iTipMessage->recipient = $orgaizer;
223+
} else {
224+
$iTipMessage->recipient = $attendee;
225+
}
226+
$iTipMessage->sender = $attendee;
227+
} else if($iTipMessage->method === 'CANCEL') {
228+
$iTipMessage->recipient = $attendee;
229+
$iTipMessage->sender = $orgaizer;
230+
}
231+
$iTipMessage->uid = isset($vEvent->{'UID'}) ? $vEvent->{'UID'}->getValue() : '';
232+
$iTipMessage->component = 'VEVENT';
233+
$iTipMessage->sequence = isset($vEvent->{'SEQUENCE'}) ? (int)$vEvent->{'SEQUENCE'}->getValue() : 0;
234+
$iTipMessage->message = $vObject;
235+
$schedulingPlugin->scheduleLocalDelivery($iTipMessage);
236+
}
180237
}

apps/dav/lib/CalDAV/CalendarProvider.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
namespace OCA\DAV\CalDAV;
2727

2828
use OCP\Calendar\ICalendarProvider;
29+
use OCP\Calendar\ICreateFromString;
2930
use OCP\IConfig;
3031
use OCP\IL10N;
3132
use Psr\Log\LoggerInterface;

apps/dav/tests/unit/CalDAV/CalendarImplTest.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@
2525
*/
2626
namespace OCA\DAV\Tests\unit\CalDAV;
2727

28+
use OCA\DAV\CalDAV\Auth\CustomPrincipalPlugin;
2829
use OCA\DAV\CalDAV\CalDavBackend;
2930
use OCA\DAV\CalDAV\Calendar;
3031
use OCA\DAV\CalDAV\CalendarImpl;
32+
use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer;
33+
use OCA\DAV\CalDAV\Schedule\Plugin;
34+
use PHPUnit\Framework\MockObject\MockObject;
3135

3236
class CalendarImplTest extends \Test\TestCase {
3337

@@ -51,6 +55,7 @@ protected function setUp(): void {
5155
'id' => 'fancy_id_123',
5256
'{DAV:}displayname' => 'user readable name 123',
5357
'{http://apple.com/ns/ical/}calendar-color' => '#AABBCC',
58+
'uri' => '/this/is/a/uri'
5459
];
5560
$this->backend = $this->createMock(CalDavBackend::class);
5661

@@ -125,4 +130,78 @@ public function testGetPermissionAll() {
125130

126131
$this->assertEquals(31, $this->calendarImpl->getPermissions());
127132
}
133+
134+
public function testHandleImipMessage(): void {
135+
$invitationResponseServer = $this->createConfiguredMock(InvitationResponseServer::class, [
136+
'server' => $this->createConfiguredMock(CalDavBackend::class, [
137+
'getPlugin' => [
138+
'auth' => $this->createMock(CustomPrincipalPlugin::class),
139+
'schedule' => $this->createMock(Plugin::class)
140+
]
141+
])
142+
]);
143+
144+
$message = <<<EOF
145+
BEGIN:VCALENDAR
146+
PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
147+
METHOD:REPLY
148+
VERSION:2.0
149+
BEGIN:VEVENT
150+
ATTENDEE;PARTSTAT=mailto:lewis@stardew-tent-living.com:ACCEPTED
151+
ORGANIZER:mailto:pierre@generalstore.com
152+
UID:aUniqueUid
153+
SEQUENCE:2
154+
REQUEST-STATUS:2.0;Success
155+
%sEND:VEVENT
156+
END:VCALENDAR
157+
EOF;
158+
159+
/** @var CustomPrincipalPlugin|MockObject $authPlugin */
160+
$authPlugin = $invitationResponseServer->server->getPlugin('auth');
161+
$authPlugin->expects(self::once())
162+
->method('setPrincipalUri')
163+
->with($this->calendar->getPrincipalURI());
164+
165+
/** @var Plugin|MockObject $schedulingPlugin */
166+
$schedulingPlugin = $invitationResponseServer->server->getPlugin('caldav-schedule');
167+
$schedulingPlugin->expects(self::once())
168+
->method('setPathOfCalendarObjectChange')
169+
->with('fullcalendarname');
170+
}
171+
172+
public function testHandleImipMessageNoCalendarUri(): void {
173+
$invitationResponseServer = $this->createConfiguredMock(InvitationResponseServer::class, [
174+
'server' => $this->createConfiguredMock(CalDavBackend::class, [
175+
'getPlugin' => [
176+
'auth' => $this->createMock(CustomPrincipalPlugin::class),
177+
'schedule' => $this->createMock(Plugin::class)
178+
]
179+
])
180+
]);
181+
182+
$message = <<<EOF
183+
BEGIN:VCALENDAR
184+
PRODID:-//Nextcloud/Nextcloud CalDAV Server//EN
185+
METHOD:REPLY
186+
VERSION:2.0
187+
BEGIN:VEVENT
188+
ATTENDEE;PARTSTAT=mailto:lewis@stardew-tent-living.com:ACCEPTED
189+
ORGANIZER:mailto:pierre@generalstore.com
190+
UID:aUniqueUid
191+
SEQUENCE:2
192+
REQUEST-STATUS:2.0;Success
193+
%sEND:VEVENT
194+
END:VCALENDAR
195+
EOF;
196+
197+
/** @var CustomPrincipalPlugin|MockObject $authPlugin */
198+
$authPlugin = $invitationResponseServer->server->getPlugin('auth');
199+
$authPlugin->expects(self::once())
200+
->method('setPrincipalUri')
201+
->with($this->calendar->getPrincipalURI());
202+
203+
unset($this->calendarInfo['uri']);
204+
$this->expectException('CalendarException');
205+
$this->calendarImpl->handleIMipMessage('filename.ics', $message);
206+
}
128207
}

0 commit comments

Comments
 (0)