Skip to content

Commit e349a63

Browse files
feat: restrict calendar invitation participants
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
1 parent 56952e4 commit e349a63

File tree

4 files changed

+287
-3
lines changed

4 files changed

+287
-3
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,18 @@ public function schedule(Message $iTipMessage) {
124124
$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
125125
return;
126126
}
127+
128+
// Check if external attendees are disabled
129+
$externalAttendeesDisabled = $this->config->getValueBool('dav', 'caldav_external_attendees_disabled', false);
130+
if ($externalAttendeesDisabled && !$this->imipService->isSystemUser($recipient)) {
131+
$this->logger->debug('Invitation not sent to external attendee (external attendees disabled)', [
132+
'uid' => $iTipMessage->uid,
133+
'attendee' => $recipient,
134+
]);
135+
$iTipMessage->scheduleStatus = '5.0; External attendees are disabled';
136+
return;
137+
}
138+
127139
$recipientName = $iTipMessage->recipientName ? (string)$iTipMessage->recipientName : null;
128140

129141
$newEvents = $iTipMessage->message;

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,16 @@ public function getLastOccurrence(VCalendar $vObject) {
873873
return $dtStart->getDateTime()->getTimeStamp();
874874
}
875875

876+
/**
877+
* Check if an email address belongs to a system user
878+
*
879+
* @param string $email
880+
* @return bool True if the email belongs to a system user, false otherwise
881+
*/
882+
public function isSystemUser(string $email): bool {
883+
return !empty($this->userManager->getByEmail($email));
884+
}
885+
876886
/**
877887
* @param Property $attendee
878888
*/

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

Lines changed: 240 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ public function testDeliveryNoSignificantChange(): void {
156156
$message->senderName = 'Mr. Wizard';
157157
$message->recipient = 'mailto:' . 'frodo@hobb.it';
158158
$message->significantChange = false;
159+
160+
$this->config->expects(self::never())
161+
->method('getValueBool');
162+
159163
$this->plugin->schedule($message);
160164
$this->assertEquals('1.0', $message->getScheduleStatus());
161165
}
@@ -203,6 +207,17 @@ public function testParsingSingle(): void {
203207
$this->service->expects(self::once())
204208
->method('getLastOccurrence')
205209
->willReturn(1496912700);
210+
$this->config->expects(self::exactly(2))
211+
->method('getValueBool')
212+
->willReturnCallback(function ($app, $key, $default) {
213+
if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') {
214+
return false;
215+
}
216+
if ($app === 'core' && $key === 'mail_providers_enabled') {
217+
return false;
218+
}
219+
return $default;
220+
});
206221
$this->mailer->expects(self::once())
207222
->method('validateMailAddress')
208223
->with('frodo@hobb.it')
@@ -310,6 +325,10 @@ public function testAttendeeIsResource(): void {
310325
$this->service->expects(self::once())
311326
->method('getLastOccurrence')
312327
->willReturn(1496912700);
328+
$this->config->expects(self::once())
329+
->method('getValueBool')
330+
->with('dav', 'caldav_external_attendees_disabled', false)
331+
->willReturn(false);
313332
$this->mailer->expects(self::once())
314333
->method('validateMailAddress')
315334
->with('the-shire@hobb.it')
@@ -388,6 +407,10 @@ public function testAttendeeIsCircle(): void {
388407
$this->service->expects(self::once())
389408
->method('getLastOccurrence')
390409
->willReturn(1496912700);
410+
$this->config->expects(self::once())
411+
->method('getValueBool')
412+
->with('dav', 'caldav_external_attendees_disabled', false)
413+
->willReturn(false);
391414
$this->mailer->expects(self::once())
392415
->method('validateMailAddress')
393416
->with('circle+82utEV1Fle8wvxndZLK5TVAPtxj8IIe@middle.earth')
@@ -493,6 +516,17 @@ public function testParsingRecurrence(): void {
493516
$this->service->expects(self::once())
494517
->method('getLastOccurrence')
495518
->willReturn(1496912700);
519+
$this->config->expects(self::exactly(2))
520+
->method('getValueBool')
521+
->willReturnCallback(function ($app, $key, $default) {
522+
if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') {
523+
return false;
524+
}
525+
if ($app === 'core' && $key === 'mail_providers_enabled') {
526+
return false;
527+
}
528+
return $default;
529+
});
496530
$this->mailer->expects(self::once())
497531
->method('validateMailAddress')
498532
->with('frodo@hobb.it')
@@ -745,6 +779,17 @@ public function testMailProviderSend(): void {
745779
$this->service->expects(self::once())
746780
->method('getLastOccurrence')
747781
->willReturn(1496912700);
782+
$this->config->expects(self::exactly(2))
783+
->method('getValueBool')
784+
->willReturnCallback(function ($app, $key, $default) {
785+
if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') {
786+
return false;
787+
}
788+
if ($app === 'core' && $key === 'mail_providers_enabled') {
789+
return true;
790+
}
791+
return $default;
792+
});
748793
$this->service->expects(self::once())
749794
->method('getCurrentAttendee')
750795
->with($message)
@@ -896,10 +941,17 @@ public function testMailProviderDisabled(): void {
896941
->method('getValueString')
897942
->with('dav', 'invitation_link_recipients', 'yes')
898943
->willReturn('yes');
899-
$this->config->expects(self::once())
944+
$this->config->expects(self::exactly(2))
900945
->method('getValueBool')
901-
->with('core', 'mail_providers_enabled', true)
902-
->willReturn(false);
946+
->willReturnCallback(function ($app, $key, $default) {
947+
if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') {
948+
return false;
949+
}
950+
if ($app === 'core' && $key === 'mail_providers_enabled') {
951+
return false;
952+
}
953+
return $default;
954+
});
903955
$this->service->expects(self::once())
904956
->method('createInvitationToken')
905957
->with($message, $newVevent, 1496912700)
@@ -947,6 +999,17 @@ public function testNoOldEvent(): void {
947999
$this->service->expects(self::once())
9481000
->method('getLastOccurrence')
9491001
->willReturn(1496912700);
1002+
$this->config->expects(self::exactly(2))
1003+
->method('getValueBool')
1004+
->willReturnCallback(function ($app, $key, $default) {
1005+
if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') {
1006+
return false;
1007+
}
1008+
if ($app === 'core' && $key === 'mail_providers_enabled') {
1009+
return false;
1010+
}
1011+
return $default;
1012+
});
9501013
$this->mailer->expects(self::once())
9511014
->method('validateMailAddress')
9521015
->with('frodo@hobb.it')
@@ -1044,6 +1107,17 @@ public function testNoButtons(): void {
10441107
$this->service->expects(self::once())
10451108
->method('getLastOccurrence')
10461109
->willReturn(1496912700);
1110+
$this->config->expects(self::exactly(2))
1111+
->method('getValueBool')
1112+
->willReturnCallback(function ($app, $key, $default) {
1113+
if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') {
1114+
return false;
1115+
}
1116+
if ($app === 'core' && $key === 'mail_providers_enabled') {
1117+
return false;
1118+
}
1119+
return $default;
1120+
});
10471121
$this->mailer->expects(self::once())
10481122
->method('validateMailAddress')
10491123
->with('frodo@hobb.it')
@@ -1107,4 +1181,167 @@ public function testNoButtons(): void {
11071181
$this->plugin->schedule($message);
11081182
$this->assertEquals('1.1', $message->getScheduleStatus());
11091183
}
1184+
1185+
public function testExternalAttendeesDisabledForExternalUser(): void {
1186+
$message = new Message();
1187+
$message->method = 'REQUEST';
1188+
$newVCalendar = new VCalendar();
1189+
$newVevent = new VEvent($newVCalendar, 'one', array_merge([
1190+
'UID' => 'uid-1234',
1191+
'SEQUENCE' => 1,
1192+
'SUMMARY' => 'Fellowship meeting',
1193+
'DTSTART' => new \DateTime('2016-01-01 00:00:00')
1194+
], []));
1195+
$newVevent->add('ORGANIZER', 'mailto:gandalf@wiz.ard');
1196+
$newVevent->add('ATTENDEE', 'mailto:external@example.com', ['RSVP' => 'TRUE', 'CN' => 'External User']);
1197+
$message->message = $newVCalendar;
1198+
$message->sender = 'mailto:gandalf@wiz.ard';
1199+
$message->senderName = 'Mr. Wizard';
1200+
$message->recipient = 'mailto:external@example.com';
1201+
1202+
$this->service->expects(self::once())
1203+
->method('getLastOccurrence')
1204+
->willReturn(1496912700);
1205+
$this->config->expects(self::once())
1206+
->method('getValueBool')
1207+
->with('dav', 'caldav_external_attendees_disabled', false)
1208+
->willReturn(true);
1209+
$this->service->expects(self::once())
1210+
->method('isSystemUser')
1211+
->with('external@example.com')
1212+
->willReturn(false);
1213+
$this->eventComparisonService->expects(self::never())
1214+
->method('findModified');
1215+
$this->service->expects(self::never())
1216+
->method('getCurrentAttendee');
1217+
$this->mailer->expects(self::once())
1218+
->method('validateMailAddress')
1219+
->willReturn(true);
1220+
$this->mailer->expects(self::never())
1221+
->method('send');
1222+
1223+
$this->plugin->schedule($message);
1224+
$this->assertEquals('5.0', $message->getScheduleStatus());
1225+
}
1226+
1227+
public function testExternalAttendeesDisabledForSystemUser(): void {
1228+
$message = new Message();
1229+
$message->method = 'REQUEST';
1230+
$newVCalendar = new VCalendar();
1231+
$newVevent = new VEvent($newVCalendar, 'one', array_merge([
1232+
'UID' => 'uid-1234',
1233+
'SEQUENCE' => 1,
1234+
'SUMMARY' => 'Fellowship meeting',
1235+
'DTSTART' => new \DateTime('2016-01-01 00:00:00')
1236+
], []));
1237+
$newVevent->add('ORGANIZER', 'mailto:gandalf@wiz.ard');
1238+
$newVevent->add('ATTENDEE', 'mailto:frodo@hobb.it', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
1239+
$message->message = $newVCalendar;
1240+
$message->sender = 'mailto:gandalf@wiz.ard';
1241+
$message->senderName = 'Mr. Wizard';
1242+
$message->recipient = 'mailto:frodo@hobb.it';
1243+
1244+
$oldVCalendar = new VCalendar();
1245+
$oldVEvent = new VEvent($oldVCalendar, 'one', [
1246+
'UID' => 'uid-1234',
1247+
'SEQUENCE' => 0,
1248+
'SUMMARY' => 'Fellowship meeting',
1249+
'DTSTART' => new \DateTime('2016-01-01 00:00:00')
1250+
]);
1251+
$oldVEvent->add('ORGANIZER', 'mailto:gandalf@wiz.ard');
1252+
$oldVEvent->add('ATTENDEE', 'mailto:frodo@hobb.it', ['RSVP' => 'TRUE', 'CN' => 'Frodo']);
1253+
$oldVCalendar->add($oldVEvent);
1254+
1255+
$data = ['invitee_name' => 'Mr. Wizard',
1256+
'meeting_title' => 'Fellowship meeting',
1257+
'attendee_name' => 'frodo@hobb.it'
1258+
];
1259+
$attendees = $newVevent->select('ATTENDEE');
1260+
$atnd = '';
1261+
foreach ($attendees as $attendee) {
1262+
if (strcasecmp($attendee->getValue(), $message->recipient) === 0) {
1263+
$atnd = $attendee;
1264+
}
1265+
}
1266+
$this->plugin->setVCalendar($oldVCalendar);
1267+
$this->service->expects(self::once())
1268+
->method('getLastOccurrence')
1269+
->willReturn(1496912700);
1270+
$this->config->expects(self::exactly(2))
1271+
->method('getValueBool')
1272+
->willReturnCallback(function ($app, $key, $default) {
1273+
if ($app === 'dav' && $key === 'caldav_external_attendees_disabled') {
1274+
return true;
1275+
}
1276+
if ($app === 'core' && $key === 'mail_providers_enabled') {
1277+
return false;
1278+
}
1279+
return $default;
1280+
});
1281+
$this->service->expects(self::once())
1282+
->method('isSystemUser')
1283+
->with('frodo@hobb.it')
1284+
->willReturn(true);
1285+
$this->eventComparisonService->expects(self::once())
1286+
->method('findModified')
1287+
->willReturn(['new' => [$newVevent], 'old' => [$oldVEvent]]);
1288+
$this->service->expects(self::once())
1289+
->method('getCurrentAttendee')
1290+
->with($message)
1291+
->willReturn($atnd);
1292+
$this->service->expects(self::once())
1293+
->method('isRoomOrResource')
1294+
->with($atnd)
1295+
->willReturn(false);
1296+
$this->service->expects(self::once())
1297+
->method('isCircle')
1298+
->with($atnd)
1299+
->willReturn(false);
1300+
$this->service->expects(self::once())
1301+
->method('buildBodyData')
1302+
->with($newVevent, $oldVEvent)
1303+
->willReturn($data);
1304+
$this->user->expects(self::any())
1305+
->method('getUID')
1306+
->willReturn('user1');
1307+
$this->user->expects(self::any())
1308+
->method('getDisplayName')
1309+
->willReturn('Mr. Wizard');
1310+
$this->userSession->expects(self::any())
1311+
->method('getUser')
1312+
->willReturn($this->user);
1313+
$this->service->expects(self::once())
1314+
->method('getFrom');
1315+
$this->service->expects(self::once())
1316+
->method('addSubjectAndHeading')
1317+
->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting', true);
1318+
$this->service->expects(self::once())
1319+
->method('addBulletList')
1320+
->with($this->emailTemplate, $newVevent, $data);
1321+
$this->service->expects(self::once())
1322+
->method('getAttendeeRsvpOrReqForParticipant')
1323+
->willReturn(true);
1324+
$this->config->expects(self::once())
1325+
->method('getValueString')
1326+
->with('dav', 'invitation_link_recipients', 'yes')
1327+
->willReturn('yes');
1328+
$this->service->expects(self::once())
1329+
->method('createInvitationToken')
1330+
->with($message, $newVevent, 1496912700)
1331+
->willReturn('token');
1332+
$this->service->expects(self::once())
1333+
->method('addResponseButtons')
1334+
->with($this->emailTemplate, 'token');
1335+
$this->service->expects(self::once())
1336+
->method('addMoreOptionsButton')
1337+
->with($this->emailTemplate, 'token');
1338+
$this->mailer->expects(self::once())
1339+
->method('validateMailAddress')
1340+
->willReturn(true);
1341+
$this->mailer->expects(self::once())
1342+
->method('send')
1343+
->willReturn([]);
1344+
$this->plugin->schedule($message);
1345+
$this->assertEquals('1.1', $message->getScheduleStatus());
1346+
}
11101347
}

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,31 @@ public function testGetFrom(): void {
181181
$this->assertEquals($expected, $actual);
182182
}
183183

184+
public function testIsSystemUserWhenUserExists(): void {
185+
$email = 'user@example.com';
186+
$user = $this->createMock(\OCP\IUser::class);
187+
188+
$this->userManager->expects(self::once())
189+
->method('getByEmail')
190+
->with($email)
191+
->willReturn([$user]);
192+
193+
$result = $this->service->isSystemUser($email);
194+
$this->assertTrue($result);
195+
}
196+
197+
public function testIsSystemUserWhenUserDoesNotExist(): void {
198+
$email = 'external@example.com';
199+
200+
$this->userManager->expects(self::once())
201+
->method('getByEmail')
202+
->with($email)
203+
->willReturn([]);
204+
205+
$result = $this->service->isSystemUser($email);
206+
$this->assertFalse($result);
207+
}
208+
184209
public function testBuildBodyDataCreated(): void {
185210

186211
// construct l10n return(s)

0 commit comments

Comments
 (0)