Skip to content

Commit bd4e802

Browse files
feat: restrict calendar invitation participants
Signed-off-by: SebastianKrupinski <[email protected]>
1 parent 5f02ba6 commit bd4e802

File tree

4 files changed

+301
-6
lines changed

4 files changed

+301
-6
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,18 @@ public function schedule(Message $iTipMessage) {
138138
$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
139139
return;
140140
}
141+
142+
// Check if external attendees are disabled
143+
$externalAttendeesDisabled = $this->config->getValueBool('dav', 'caldav_external_attendees_disabled', false);
144+
if ($externalAttendeesDisabled && !$this->imipService->isSystemUser($recipient)) {
145+
$this->logger->debug('Invitation not sent to external attendee (external attendees disabled)', [
146+
'uid' => $iTipMessage->uid,
147+
'attendee' => $recipient,
148+
]);
149+
$iTipMessage->scheduleStatus = '5.0; External attendees are disabled';
150+
return;
151+
}
152+
141153
$recipientName = $iTipMessage->recipientName ? (string) $iTipMessage->recipientName : null;
142154

143155
$newEvents = $iTipMessage->message;

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use OCP\IConfig;
1515
use OCP\IDBConnection;
1616
use OCP\IL10N;
17+
use OCP\IUserManager;
1718
use OCP\L10N\IFactory as L10NFactory;
1819
use OCP\Mail\IEMailTemplate;
1920
use OCP\Security\ISecureRandom;
@@ -35,7 +36,8 @@ class IMipService {
3536
private L10NFactory $l10nFactory;
3637
private IL10N $l10n;
3738
private ITimeFactory $timeFactory;
38-
39+
private IUserManager $userManager;
40+
3941
/** @var string[] */
4042
private const STRING_DIFF = [
4143
'meeting_title' => 'SUMMARY',
@@ -49,13 +51,16 @@ public function __construct(URLGenerator $urlGenerator,
4951
IDBConnection $db,
5052
ISecureRandom $random,
5153
L10NFactory $l10nFactory,
52-
ITimeFactory $timeFactory) {
54+
ITimeFactory $timeFactory,
55+
IUserManager $userManager,
56+
) {
5357
$this->urlGenerator = $urlGenerator;
5458
$this->config = $config;
5559
$this->db = $db;
5660
$this->random = $random;
5761
$this->l10nFactory = $l10nFactory;
5862
$this->timeFactory = $timeFactory;
63+
$this->userManager = $userManager;
5964
$language = $this->l10nFactory->findGenericLanguage();
6065
$locale = $this->l10nFactory->findLocale($language);
6166
$this->l10n = $this->l10nFactory->get('dav', $language, $locale);
@@ -1182,6 +1187,16 @@ public function getReplyingAttendee(Message $iTipMessage): ?Property {
11821187
return null;
11831188
}
11841189

1190+
/**
1191+
* Check if an email address belongs to a system user
1192+
*
1193+
* @param string $email
1194+
* @return bool True if the email belongs to a system user, false otherwise
1195+
*/
1196+
public function isSystemUser(string $email): bool {
1197+
return !empty($this->userManager->getByEmail($email));
1198+
}
1199+
11851200
public function isRoomOrResource(Property $attendee): bool {
11861201
$cuType = $attendee->offsetGet('CUTYPE');
11871202
if (!$cuType instanceof Parameter) {

apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php

Lines changed: 240 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ public function testDeliveryNoSignificantChange(): void {
157157
$message->senderName = 'Mr. Wizard';
158158
$message->recipient = 'mailto:' . '[email protected]';
159159
$message->significantChange = false;
160+
161+
$this->config->expects(self::never())
162+
->method('getValueBool');
163+
160164
$this->plugin->schedule($message);
161165
$this->assertEquals('1.0', $message->getScheduleStatus());
162166
}
@@ -204,6 +208,17 @@ public function testParsingSingle(): void {
204208
$this->service->expects(self::once())
205209
->method('getLastOccurrence')
206210
->willReturn(1496912700);
211+
$this->config->expects(self::exactly(2))
212+
->method('getValueBool')
213+
->willReturnCallback(function ($app, $key, $default) {
214+
if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') {
215+
return false;
216+
}
217+
if ($app === 'core' && $key === 'mail_providers_enabled') {
218+
return false;
219+
}
220+
return $default;
221+
});
207222
$this->mailer->expects(self::once())
208223
->method('validateMailAddress')
209224
@@ -311,6 +326,10 @@ public function testAttendeeIsResource(): void {
311326
$this->service->expects(self::once())
312327
->method('getLastOccurrence')
313328
->willReturn(1496912700);
329+
$this->config->expects(self::once())
330+
->method('getValueBool')
331+
->with('dav', 'caldav_external_attendees_disabled', false)
332+
->willReturn(false);
314333
$this->mailer->expects(self::once())
315334
->method('validateMailAddress')
316335
@@ -389,6 +408,10 @@ public function testAttendeeIsCircle(): void {
389408
$this->service->expects(self::once())
390409
->method('getLastOccurrence')
391410
->willReturn(1496912700);
411+
$this->config->expects(self::once())
412+
->method('getValueBool')
413+
->with('dav', 'caldav_external_attendees_disabled', false)
414+
->willReturn(false);
392415
$this->mailer->expects(self::once())
393416
->method('validateMailAddress')
394417
@@ -494,6 +517,17 @@ public function testParsingRecurrence(): void {
494517
$this->service->expects(self::once())
495518
->method('getLastOccurrence')
496519
->willReturn(1496912700);
520+
$this->config->expects(self::exactly(2))
521+
->method('getValueBool')
522+
->willReturnCallback(function ($app, $key, $default) {
523+
if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') {
524+
return false;
525+
}
526+
if ($app === 'core' && $key === 'mail_providers_enabled') {
527+
return false;
528+
}
529+
return $default;
530+
});
497531
$this->mailer->expects(self::once())
498532
->method('validateMailAddress')
499533
@@ -746,6 +780,17 @@ public function testMailProviderSend(): void {
746780
$this->service->expects(self::once())
747781
->method('getLastOccurrence')
748782
->willReturn(1496912700);
783+
$this->config->expects(self::exactly(2))
784+
->method('getValueBool')
785+
->willReturnCallback(function ($app, $key, $default) {
786+
if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') {
787+
return false;
788+
}
789+
if ($app === 'core' && $key === 'mail_providers_enabled') {
790+
return true;
791+
}
792+
return $default;
793+
});
749794
$this->service->expects(self::once())
750795
->method('getCurrentAttendee')
751796
->with($message)
@@ -897,10 +942,17 @@ public function testMailProviderDisabled(): void {
897942
->method('getValueString')
898943
->with('dav', 'invitation_link_recipients', 'yes')
899944
->willReturn('yes');
900-
$this->config->expects(self::once())
945+
$this->config->expects(self::exactly(2))
901946
->method('getValueBool')
902-
->with('core', 'mail_providers_enabled', true)
903-
->willReturn(false);
947+
->willReturnCallback(function ($app, $key, $default) {
948+
if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') {
949+
return false;
950+
}
951+
if ($app === 'core' && $key === 'mail_providers_enabled') {
952+
return false;
953+
}
954+
return $default;
955+
});
904956
$this->service->expects(self::once())
905957
->method('createInvitationToken')
906958
->with($message, $newVevent, 1496912700)
@@ -948,6 +1000,17 @@ public function testNoOldEvent(): void {
9481000
$this->service->expects(self::once())
9491001
->method('getLastOccurrence')
9501002
->willReturn(1496912700);
1003+
$this->config->expects(self::exactly(2))
1004+
->method('getValueBool')
1005+
->willReturnCallback(function ($app, $key, $default) {
1006+
if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') {
1007+
return false;
1008+
}
1009+
if ($app === 'core' && $key === 'mail_providers_enabled') {
1010+
return false;
1011+
}
1012+
return $default;
1013+
});
9511014
$this->mailer->expects(self::once())
9521015
->method('validateMailAddress')
9531016
@@ -1045,6 +1108,17 @@ public function testNoButtons(): void {
10451108
$this->service->expects(self::once())
10461109
->method('getLastOccurrence')
10471110
->willReturn(1496912700);
1111+
$this->config->expects(self::exactly(2))
1112+
->method('getValueBool')
1113+
->willReturnCallback(function ($app, $key, $default) {
1114+
if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') {
1115+
return false;
1116+
}
1117+
if ($app === 'core' && $key === 'mail_providers_enabled') {
1118+
return false;
1119+
}
1120+
return $default;
1121+
});
10481122
$this->mailer->expects(self::once())
10491123
->method('validateMailAddress')
10501124
@@ -1108,4 +1182,167 @@ public function testNoButtons(): void {
11081182
$this->plugin->schedule($message);
11091183
$this->assertEquals('1.1', $message->getScheduleStatus());
11101184
}
1185+
1186+
public function testExternalAttendeesDisabledForExternalUser(): void {
1187+
$message = new Message();
1188+
$message->method = 'REQUEST';
1189+
$newVCalendar = new VCalendar();
1190+
$newVevent = new VEvent($newVCalendar, 'one', array_merge([
1191+
'UID' => 'uid-1234',
1192+
'SEQUENCE' => 1,
1193+
'SUMMARY' => 'Fellowship meeting',
1194+
'DTSTART' => new \DateTime('2016-01-01 00:00:00')
1195+
], []));
1196+
$newVevent->add('ORGANIZER', 'mailto:[email protected]');
1197+
$newVevent->add('ATTENDEE', 'mailto:[email protected]', ['RSVP' => 'TRUE', 'CN' => 'External User']);
1198+
$message->message = $newVCalendar;
1199+
$message->sender = 'mailto:[email protected]';
1200+
$message->senderName = 'Mr. Wizard';
1201+
$message->recipient = 'mailto:[email protected]';
1202+
1203+
$this->service->expects(self::once())
1204+
->method('getLastOccurrence')
1205+
->willReturn(1496912700);
1206+
$this->config->expects(self::once())
1207+
->method('getValueBool')
1208+
->with('dav', 'caldav_external_attendees_disabled', false)
1209+
->willReturn(true);
1210+
$this->service->expects(self::once())
1211+
->method('isSystemUser')
1212+
1213+
->willReturn(false);
1214+
$this->eventComparisonService->expects(self::never())
1215+
->method('findModified');
1216+
$this->service->expects(self::never())
1217+
->method('getCurrentAttendee');
1218+
$this->mailer->expects(self::once())
1219+
->method('validateMailAddress')
1220+
->willReturn(true);
1221+
$this->mailer->expects(self::never())
1222+
->method('send');
1223+
1224+
$this->plugin->schedule($message);
1225+
$this->assertEquals('5.0', $message->getScheduleStatus());
1226+
}
1227+
1228+
public function testExternalAttendeesDisabledForSystemUser(): void {
1229+
$message = new Message();
1230+
$message->method = 'REQUEST';
1231+
$newVCalendar = new VCalendar();
1232+
$newVevent = new VEvent($newVCalendar, 'one', array_merge([
1233+
'UID' => 'uid-1234',
1234+
'SEQUENCE' => 1,
1235+
'SUMMARY' => 'Fellowship meeting',
1236+
'DTSTART' => new \DateTime('2016-01-01 00:00:00')
1237+
], []));
1238+
$newVevent->add('ORGANIZER', 'mailto:[email protected]');
1239+
$newVevent->add('ATTENDEE', 'mailto:[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
1240+
$message->message = $newVCalendar;
1241+
$message->sender = 'mailto:[email protected]';
1242+
$message->senderName = 'Mr. Wizard';
1243+
$message->recipient = 'mailto:[email protected]';
1244+
1245+
$oldVCalendar = new VCalendar();
1246+
$oldVEvent = new VEvent($oldVCalendar, 'one', [
1247+
'UID' => 'uid-1234',
1248+
'SEQUENCE' => 0,
1249+
'SUMMARY' => 'Fellowship meeting',
1250+
'DTSTART' => new \DateTime('2016-01-01 00:00:00')
1251+
]);
1252+
$oldVEvent->add('ORGANIZER', 'mailto:[email protected]');
1253+
$oldVEvent->add('ATTENDEE', 'mailto:[email protected]', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
1254+
$oldVCalendar->add($oldVEvent);
1255+
1256+
$data = ['invitee_name' => 'Mr. Wizard',
1257+
'meeting_title' => 'Fellowship meeting',
1258+
'attendee_name' => '[email protected]'
1259+
];
1260+
$attendees = $newVevent->select('ATTENDEE');
1261+
$atnd = '';
1262+
foreach ($attendees as $attendee) {
1263+
if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
1264+
$atnd = $attendee;
1265+
}
1266+
}
1267+
$this->plugin->setVCalendar($oldVCalendar);
1268+
$this->service->expects(self::once())
1269+
->method('getLastOccurrence')
1270+
->willReturn(1496912700);
1271+
$this->config->expects(self::exactly(2))
1272+
->method('getValueBool')
1273+
->willReturnCallback(function ($app, $key, $default) {
1274+
if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') {
1275+
return true;
1276+
}
1277+
if ($app === 'core' && $key === 'mail_providers_enabled') {
1278+
return false;
1279+
}
1280+
return $default;
1281+
});
1282+
$this->service->expects(self::once())
1283+
->method('isSystemUser')
1284+
1285+
->willReturn(true);
1286+
$this->eventComparisonService->expects(self::once())
1287+
->method('findModified')
1288+
->willReturn(['new' => [$newVevent], 'old' => [$oldVEvent]]);
1289+
$this->service->expects(self::once())
1290+
->method('getCurrentAttendee')
1291+
->with($message)
1292+
->willReturn($atnd);
1293+
$this->service->expects(self::once())
1294+
->method('isRoomOrResource')
1295+
->with($atnd)
1296+
->willReturn(false);
1297+
$this->service->expects(self::once())
1298+
->method('isCircle')
1299+
->with($atnd)
1300+
->willReturn(false);
1301+
$this->service->expects(self::once())
1302+
->method('buildBodyData')
1303+
->with($newVevent, $oldVEvent)
1304+
->willReturn($data);
1305+
$this->user->expects(self::any())
1306+
->method('getUID')
1307+
->willReturn('user1');
1308+
$this->user->expects(self::any())
1309+
->method('getDisplayName')
1310+
->willReturn('Mr. Wizard');
1311+
$this->userSession->expects(self::any())
1312+
->method('getUser')
1313+
->willReturn($this->user);
1314+
$this->service->expects(self::once())
1315+
->method('getFrom');
1316+
$this->service->expects(self::once())
1317+
->method('addSubjectAndHeading')
1318+
->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting', true);
1319+
$this->service->expects(self::once())
1320+
->method('addBulletList')
1321+
->with($this->emailTemplate, $newVevent, $data);
1322+
$this->service->expects(self::once())
1323+
->method('getAttendeeRsvpOrReqForParticipant')
1324+
->willReturn(true);
1325+
$this->config->expects(self::once())
1326+
->method('getValueString')
1327+
->with('dav', 'invitation_link_recipients', 'yes')
1328+
->willReturn('yes');
1329+
$this->service->expects(self::once())
1330+
->method('createInvitationToken')
1331+
->with($message, $newVevent, 1496912700)
1332+
->willReturn('token');
1333+
$this->service->expects(self::once())
1334+
->method('addResponseButtons')
1335+
->with($this->emailTemplate, 'token');
1336+
$this->service->expects(self::once())
1337+
->method('addMoreOptionsButton')
1338+
->with($this->emailTemplate, 'token');
1339+
$this->mailer->expects(self::once())
1340+
->method('validateMailAddress')
1341+
->willReturn(true);
1342+
$this->mailer->expects(self::once())
1343+
->method('send')
1344+
->willReturn([]);
1345+
$this->plugin->schedule($message);
1346+
$this->assertEquals('1.1', $message->getScheduleStatus());
1347+
}
11111348
}

0 commit comments

Comments
 (0)