Passed
Push — master ( bce941...17d0da )
by John
67:35 queued 55:34
created

Backend::triggerActivityUser()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 9
c 1
b 0
f 0
nc 1
nop 5
dl 0
loc 15
rs 9.9666
1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * @copyright Copyright (c) 2021 Joas Schilling <[email protected]>
6
 *
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Roeland Jago Douma <[email protected]>
10
 * @author Thomas Citharel <[email protected]>
11
 *
12
 * @license GNU AGPL version 3 or any later version
13
 *
14
 * This program is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License as
16
 * published by the Free Software Foundation, either version 3 of the
17
 * License, or (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License
25
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26
 *
27
 */
28
29
namespace OCA\DAV\CardDAV\Activity;
30
31
use OCA\DAV\CardDAV\Activity\Provider\Addressbook;
32
use OCP\Activity\IEvent;
33
use OCP\Activity\IManager as IActivityManager;
34
use OCP\App\IAppManager;
35
use OCP\IGroup;
36
use OCP\IGroupManager;
37
use OCP\IUser;
38
use OCP\IUserSession;
39
use Sabre\CardDAV\Plugin;
40
use Sabre\VObject\Reader;
41
42
class Backend {
43
44
	/** @var IActivityManager */
45
	protected $activityManager;
46
47
	/** @var IGroupManager */
48
	protected $groupManager;
49
50
	/** @var IUserSession */
51
	protected $userSession;
52
53
	/** @var IAppManager */
54
	protected $appManager;
55
56
	public function __construct(IActivityManager $activityManager,
57
								IGroupManager $groupManager,
58
								IUserSession $userSession,
59
								IAppManager $appManager) {
60
		$this->activityManager = $activityManager;
61
		$this->groupManager = $groupManager;
62
		$this->userSession = $userSession;
63
		$this->appManager = $appManager;
64
	}
65
66
	/**
67
	 * Creates activities when an addressbook was creates
68
	 *
69
	 * @param array $addressbookData
70
	 */
71
	public function onAddressbookCreate(array $addressbookData): void {
72
		$this->triggerAddressbookActivity(Addressbook::SUBJECT_ADD, $addressbookData);
73
	}
74
75
	/**
76
	 * Creates activities when a calendar was updated
77
	 *
78
	 * @param array $addressbookData
79
	 * @param array $shares
80
	 * @param array $properties
81
	 */
82
	public function onAddressbookUpdate(array $addressbookData, array $shares, array $properties): void {
83
		$this->triggerAddressbookActivity(Addressbook::SUBJECT_UPDATE, $addressbookData, $shares, $properties);
84
	}
85
86
	/**
87
	 * Creates activities when a calendar was deleted
88
	 *
89
	 * @param array $addressbookData
90
	 * @param array $shares
91
	 */
92
	public function onAddressbookDelete(array $addressbookData, array $shares): void {
93
		$this->triggerAddressbookActivity(Addressbook::SUBJECT_DELETE, $addressbookData, $shares);
94
	}
95
96
	/**
97
	 * Creates activities for all related users when a calendar was touched
98
	 *
99
	 * @param string $action
100
	 * @param array $addressbookData
101
	 * @param array $shares
102
	 * @param array $changedProperties
103
	 */
104
	protected function triggerAddressbookActivity(string $action, array $addressbookData, array $shares = [], array $changedProperties = []): void {
105
		if (!isset($addressbookData['principaluri'])) {
106
			return;
107
		}
108
109
		$principal = explode('/', $addressbookData['principaluri']);
110
		$owner = array_pop($principal);
111
112
		$currentUser = $this->userSession->getUser();
113
		if ($currentUser instanceof IUser) {
114
			$currentUser = $currentUser->getUID();
115
		} else {
116
			$currentUser = $owner;
117
		}
118
119
		$event = $this->activityManager->generateEvent();
120
		$event->setApp('dav')
121
			->setObject('addressbook', (int) $addressbookData['id'])
122
			->setType('addressbook')
123
			->setAuthor($currentUser);
124
125
		$changedVisibleInformation = array_intersect([
126
			'{DAV:}displayname',
127
			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
128
		], array_keys($changedProperties));
129
130
		if (empty($shares) || ($action === Addressbook::SUBJECT_UPDATE && empty($changedVisibleInformation))) {
131
			$users = [$owner];
132
		} else {
133
			$users = $this->getUsersForShares($shares);
134
			$users[] = $owner;
135
		}
136
137
		foreach ($users as $user) {
138
			$event->setAffectedUser($user)
139
				->setSubject(
140
					$user === $currentUser ? $action . '_self' : $action,
141
					[
142
						'actor' => $currentUser,
143
						'addressbook' => [
144
							'id' => (int) $addressbookData['id'],
145
							'uri' => $addressbookData['uri'],
146
							'name' => $addressbookData['{DAV:}displayname'],
147
						],
148
					]
149
				);
150
			$this->activityManager->publish($event);
151
		}
152
	}
153
154
	/**
155
	 * Creates activities for all related users when an addressbook was (un-)shared
156
	 *
157
	 * @param array $addressbookData
158
	 * @param array $shares
159
	 * @param array $add
160
	 * @param array $remove
161
	 */
162
	public function onAddressbookUpdateShares(array $addressbookData, array $shares, array $add, array $remove): void {
163
		$principal = explode('/', $addressbookData['principaluri']);
164
		$owner = $principal[2];
165
166
		$currentUser = $this->userSession->getUser();
167
		if ($currentUser instanceof IUser) {
168
			$currentUser = $currentUser->getUID();
169
		} else {
170
			$currentUser = $owner;
171
		}
172
173
		$event = $this->activityManager->generateEvent();
174
		$event->setApp('dav')
175
			->setObject('addressbook', (int) $addressbookData['id'])
176
			->setType('addressbook')
177
			->setAuthor($currentUser);
178
179
		foreach ($remove as $principal) {
180
			// principal:principals/users/test
181
			$parts = explode(':', $principal, 2);
182
			if ($parts[0] !== 'principal') {
183
				continue;
184
			}
185
			$principal = explode('/', $parts[1]);
186
187
			if ($principal[1] === 'users') {
188
				$this->triggerActivityUser(
189
					$principal[2],
190
					$event,
191
					$addressbookData,
192
					Addressbook::SUBJECT_UNSHARE_USER,
193
					Addressbook::SUBJECT_DELETE . '_self'
194
				);
195
196
				if ($owner !== $principal[2]) {
197
					$parameters = [
198
						'actor' => $event->getAuthor(),
199
						'addressbook' => [
200
							'id' => (int) $addressbookData['id'],
201
							'uri' => $addressbookData['uri'],
202
							'name' => $addressbookData['{DAV:}displayname'],
203
						],
204
						'user' => $principal[2],
205
					];
206
207
					if ($owner === $event->getAuthor()) {
208
						$subject = Addressbook::SUBJECT_UNSHARE_USER . '_you';
209
					} elseif ($principal[2] === $event->getAuthor()) {
210
						$subject = Addressbook::SUBJECT_UNSHARE_USER . '_self';
211
					} else {
212
						$event->setAffectedUser($event->getAuthor())
213
							->setSubject(Addressbook::SUBJECT_UNSHARE_USER . '_you', $parameters);
214
						$this->activityManager->publish($event);
215
216
						$subject = Addressbook::SUBJECT_UNSHARE_USER . '_by';
217
					}
218
219
					$event->setAffectedUser($owner)
220
						->setSubject($subject, $parameters);
221
					$this->activityManager->publish($event);
222
				}
223
			} elseif ($principal[1] === 'groups') {
224
				$this->triggerActivityGroup($principal[2], $event, $addressbookData, Addressbook::SUBJECT_UNSHARE_USER);
225
226
				$parameters = [
227
					'actor' => $event->getAuthor(),
228
					'addressbook' => [
229
						'id' => (int) $addressbookData['id'],
230
						'uri' => $addressbookData['uri'],
231
						'name' => $addressbookData['{DAV:}displayname'],
232
					],
233
					'group' => $principal[2],
234
				];
235
236
				if ($owner === $event->getAuthor()) {
237
					$subject = Addressbook::SUBJECT_UNSHARE_GROUP . '_you';
238
				} else {
239
					$event->setAffectedUser($event->getAuthor())
240
						->setSubject(Addressbook::SUBJECT_UNSHARE_GROUP . '_you', $parameters);
241
					$this->activityManager->publish($event);
242
243
					$subject = Addressbook::SUBJECT_UNSHARE_GROUP . '_by';
244
				}
245
246
				$event->setAffectedUser($owner)
247
					->setSubject($subject, $parameters);
248
				$this->activityManager->publish($event);
249
			}
250
		}
251
252
		foreach ($add as $share) {
253
			if ($this->isAlreadyShared($share['href'], $shares)) {
254
				continue;
255
			}
256
257
			// principal:principals/users/test
258
			$parts = explode(':', $share['href'], 2);
259
			if ($parts[0] !== 'principal') {
260
				continue;
261
			}
262
			$principal = explode('/', $parts[1]);
263
264
			if ($principal[1] === 'users') {
265
				$this->triggerActivityUser($principal[2], $event, $addressbookData, Addressbook::SUBJECT_SHARE_USER);
266
267
				if ($owner !== $principal[2]) {
268
					$parameters = [
269
						'actor' => $event->getAuthor(),
270
						'addressbook' => [
271
							'id' => (int) $addressbookData['id'],
272
							'uri' => $addressbookData['uri'],
273
							'name' => $addressbookData['{DAV:}displayname'],
274
						],
275
						'user' => $principal[2],
276
					];
277
278
					if ($owner === $event->getAuthor()) {
279
						$subject = Addressbook::SUBJECT_SHARE_USER . '_you';
280
					} else {
281
						$event->setAffectedUser($event->getAuthor())
282
							->setSubject(Addressbook::SUBJECT_SHARE_USER . '_you', $parameters);
283
						$this->activityManager->publish($event);
284
285
						$subject = Addressbook::SUBJECT_SHARE_USER . '_by';
286
					}
287
288
					$event->setAffectedUser($owner)
289
						->setSubject($subject, $parameters);
290
					$this->activityManager->publish($event);
291
				}
292
			} elseif ($principal[1] === 'groups') {
293
				$this->triggerActivityGroup($principal[2], $event, $addressbookData, Addressbook::SUBJECT_SHARE_USER);
294
295
				$parameters = [
296
					'actor' => $event->getAuthor(),
297
					'addressbook' => [
298
						'id' => (int) $addressbookData['id'],
299
						'uri' => $addressbookData['uri'],
300
						'name' => $addressbookData['{DAV:}displayname'],
301
					],
302
					'group' => $principal[2],
303
				];
304
305
				if ($owner === $event->getAuthor()) {
306
					$subject = Addressbook::SUBJECT_SHARE_GROUP . '_you';
307
				} else {
308
					$event->setAffectedUser($event->getAuthor())
309
						->setSubject(Addressbook::SUBJECT_SHARE_GROUP . '_you', $parameters);
310
					$this->activityManager->publish($event);
311
312
					$subject = Addressbook::SUBJECT_SHARE_GROUP . '_by';
313
				}
314
315
				$event->setAffectedUser($owner)
316
					->setSubject($subject, $parameters);
317
				$this->activityManager->publish($event);
318
			}
319
		}
320
	}
321
322
	/**
323
	 * Checks if a calendar is already shared with a principal
324
	 *
325
	 * @param string $principal
326
	 * @param array[] $shares
327
	 * @return bool
328
	 */
329
	protected function isAlreadyShared(string $principal, array $shares): bool {
330
		foreach ($shares as $share) {
331
			if ($principal === $share['href']) {
332
				return true;
333
			}
334
		}
335
336
		return false;
337
	}
338
339
	/**
340
	 * Creates the given activity for all members of the given group
341
	 *
342
	 * @param string $gid
343
	 * @param IEvent $event
344
	 * @param array $properties
345
	 * @param string $subject
346
	 */
347
	protected function triggerActivityGroup(string $gid, IEvent $event, array $properties, string $subject): void {
348
		$group = $this->groupManager->get($gid);
349
350
		if ($group instanceof IGroup) {
351
			foreach ($group->getUsers() as $user) {
352
				// Exclude current user
353
				if ($user->getUID() !== $event->getAuthor()) {
354
					$this->triggerActivityUser($user->getUID(), $event, $properties, $subject);
355
				}
356
			}
357
		}
358
	}
359
360
	/**
361
	 * Creates the given activity for the given user
362
	 *
363
	 * @param string $user
364
	 * @param IEvent $event
365
	 * @param array $properties
366
	 * @param string $subject
367
	 * @param string $subjectSelf
368
	 */
369
	protected function triggerActivityUser(string $user, IEvent $event, array $properties, string $subject, string $subjectSelf = ''): void {
370
		$event->setAffectedUser($user)
371
			->setSubject(
372
				$user === $event->getAuthor() && $subjectSelf ? $subjectSelf : $subject,
373
				[
374
					'actor' => $event->getAuthor(),
375
					'addressbook' => [
376
						'id' => (int) $properties['id'],
377
						'uri' => $properties['uri'],
378
						'name' => $properties['{DAV:}displayname'],
379
					],
380
				]
381
			);
382
383
		$this->activityManager->publish($event);
384
	}
385
386
	/**
387
	 * Creates activities when a card was created/updated/deleted
388
	 *
389
	 * @param string $action
390
	 * @param array $addressbookData
391
	 * @param array $shares
392
	 * @param array $cardData
393
	 */
394
	public function triggerCardActivity(string $action, array $addressbookData, array $shares, array $cardData): void {
395
		if (!isset($addressbookData['principaluri'])) {
396
			return;
397
		}
398
399
		$principal = explode('/', $addressbookData['principaluri']);
400
		$owner = array_pop($principal);
401
402
		$currentUser = $this->userSession->getUser();
403
		if ($currentUser instanceof IUser) {
404
			$currentUser = $currentUser->getUID();
405
		} else {
406
			$currentUser = $owner;
407
		}
408
409
		$card = $this->getCardNameAndId($cardData);
410
411
		$event = $this->activityManager->generateEvent();
412
		$event->setApp('dav')
413
			->setObject('addressbook', (int) $addressbookData['id'])
414
			->setType('card')
415
			->setAuthor($currentUser);
416
417
		$users = $this->getUsersForShares($shares);
418
		$users[] = $owner;
419
420
		// Users for share can return the owner itself if the calendar is published
421
		foreach (array_unique($users) as $user) {
422
			$params = [
423
				'actor' => $event->getAuthor(),
424
				'addressbook' => [
425
					'id' => (int) $addressbookData['id'],
426
					'uri' => $addressbookData['uri'],
427
					'name' => $addressbookData['{DAV:}displayname'],
428
				],
429
				'card' => [
430
					'id' => $card['id'],
431
					'name' => $card['name'],
432
				],
433
			];
434
435
436
			$event->setAffectedUser($user)
437
				->setSubject(
438
					$user === $currentUser ? $action . '_self' : $action,
439
					$params
440
				);
441
442
			$this->activityManager->publish($event);
443
		}
444
	}
445
446
	/**
447
	 * @param array $cardData
448
	 * @return string[]
449
	 */
450
	protected function getCardNameAndId(array $cardData): array {
451
		$vObject = Reader::read($cardData['carddata']);
452
		return ['id' => (string) $vObject->UID, 'name' => (string) ($vObject->FN ?? '')];
453
	}
454
455
	/**
456
	 * Get all users that have access to a given calendar
457
	 *
458
	 * @param array $shares
459
	 * @return string[]
460
	 */
461
	protected function getUsersForShares(array $shares): array {
462
		$users = $groups = [];
463
		foreach ($shares as $share) {
464
			$principal = explode('/', $share['{http://owncloud.org/ns}principal']);
465
			if ($principal[1] === 'users') {
466
				$users[] = $principal[2];
467
			} elseif ($principal[1] === 'groups') {
468
				$groups[] = $principal[2];
469
			}
470
		}
471
472
		if (!empty($groups)) {
473
			foreach ($groups as $gid) {
474
				$group = $this->groupManager->get($gid);
475
				if ($group instanceof IGroup) {
476
					foreach ($group->getUsers() as $user) {
477
						$users[] = $user->getUID();
478
					}
479
				}
480
			}
481
		}
482
483
		return array_unique($users);
484
	}
485
}
486