Completed
Pull Request — master (#641)
by Maxence
02:32
created

FederatedUserService::markConflict()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 33
rs 8.7697
c 0
b 0
f 0
cc 6
nc 6
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
6
/**
7
 * Circles - Bring cloud-users closer together.
8
 *
9
 * This file is licensed under the Affero General Public License version 3 or
10
 * later. See the COPYING file.
11
 *
12
 * @author Maxence Lange <[email protected]>
13
 * @copyright 2021
14
 * @license GNU AGPL version 3 or any later version
15
 *
16
 * This program is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License as
18
 * published by the Free Software Foundation, either version 3 of the
19
 * License, or (at your option) any later version.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
28
 *
29
 */
30
31
32
namespace OCA\Circles\Service;
33
34
35
use ArtificialOwl\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Logger;
36
use ArtificialOwl\MySmallPhpTools\Traits\TArrayTools;
37
use ArtificialOwl\MySmallPhpTools\Traits\TStringTools;
38
use Exception;
39
use OC;
40
use OCA\Circles\AppInfo\Application;
41
use OCA\Circles\Db\CircleRequest;
42
use OCA\Circles\Db\MemberRequest;
43
use OCA\Circles\Exceptions\CircleNotFoundException;
44
use OCA\Circles\Exceptions\ContactAddressBookNotFoundException;
45
use OCA\Circles\Exceptions\ContactFormatException;
46
use OCA\Circles\Exceptions\ContactNotFoundException;
47
use OCA\Circles\Exceptions\FederatedEventException;
48
use OCA\Circles\Exceptions\FederatedItemException;
49
use OCA\Circles\Exceptions\FederatedUserException;
50
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
51
use OCA\Circles\Exceptions\GroupNotFoundException;
52
use OCA\Circles\Exceptions\InitiatorNotConfirmedException;
53
use OCA\Circles\Exceptions\InitiatorNotFoundException;
54
use OCA\Circles\Exceptions\InvalidIdException;
55
use OCA\Circles\Exceptions\MemberNotFoundException;
56
use OCA\Circles\Exceptions\OwnerNotFoundException;
57
use OCA\Circles\Exceptions\RemoteInstanceException;
58
use OCA\Circles\Exceptions\RemoteNotFoundException;
59
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
60
use OCA\Circles\Exceptions\RequestBuilderException;
61
use OCA\Circles\Exceptions\SingleCircleNotFoundException;
62
use OCA\Circles\Exceptions\UnknownRemoteException;
63
use OCA\Circles\Exceptions\UserTypeNotFoundException;
64
use OCA\Circles\FederatedItems\CircleCreate;
65
use OCA\Circles\IFederatedUser;
66
use OCA\Circles\Model\Circle;
67
use OCA\Circles\Model\Federated\FederatedEvent;
68
use OCA\Circles\Model\Federated\RemoteInstance;
69
use OCA\Circles\Model\FederatedUser;
70
use OCA\Circles\Model\ManagedModel;
71
use OCA\Circles\Model\Member;
72
use OCP\IGroupManager;
73
use OCP\IUser;
74
use OCP\IUserManager;
75
use OCP\IUserSession;
76
77
78
/**
79
 * Class FederatedUserService
80
 *
81
 * @package OCA\Circles\Service
82
 */
83
class FederatedUserService {
84
85
86
	use TArrayTools;
87
	use TStringTools;
88
	use TNC22Logger;
89
90
91
	const CONFLICT_001 = 1;
92
	const CONFLICT_002 = 2;
93
	const CONFLICT_003 = 3;
94
	const CONFLICT_004 = 4;
95
	const CONFLICT_005 = 5;
96
97
98
	/** @var IUserSession */
99
	private $userSession;
100
101
	/** @var IUserManager */
102
	private $userManager;
103
104
	/** @var IGroupManager */
105
	private $groupManager;
106
107
	/** @var FederatedEventService */
108
	private $federatedEventService;
109
110
	/** @var MembershipService */
111
	private $membershipService;
112
113
	/** @var CircleRequest */
114
	private $circleRequest;
115
116
	/** @var MemberRequest */
117
	private $memberRequest;
118
119
	/** @var RemoteService */
120
	private $remoteService;
121
122
	/** @var RemoteStreamService */
123
	private $remoteStreamService;
124
125
	/** @var ContactService */
126
	private $contactService;
127
128
	/** @var InterfaceService */
129
	private $interfaceService;
130
131
	/** @var ConfigService */
132
	private $configService;
133
134
135
	/** @var FederatedUser */
136
	private $currentUser = null;
137
138
	/** @var FederatedUser */
139
	private $currentApp = null;
140
141
	/** @var RemoteInstance */
142
	private $remoteInstance = null;
143
144
	/** @var bool */
145
	private $bypass = false;
146
147
	/** @var bool */
148
	private $initiatedByOcc = false;
149
150
	/** @var FederatedUser */
151
	private $initiatedByAdmin = null;
152
153
154
	/**
155
	 * FederatedUserService constructor.
156
	 *
157
	 * @param IUserSession $userSession
158
	 * @param IUserManager $userManager
159
	 * @param IGroupManager $groupManager
160
	 * @param FederatedEventService $federatedEventService
161
	 * @param MembershipService $membershipService
162
	 * @param CircleRequest $circleRequest
163
	 * @param MemberRequest $memberRequest
164
	 * @param RemoteService $remoteService
165
	 * @param RemoteStreamService $remoteStreamService
166
	 * @param ContactService $contactService
167
	 * @param InterfaceService $interfaceService
168
	 * @param ConfigService $configService
169
	 */
170
	public function __construct(
171
		IUserSession $userSession,
172
		IUserManager $userManager,
173
		IGroupManager $groupManager,
174
		FederatedEventService $federatedEventService,
175
		MembershipService $membershipService,
176
		CircleRequest $circleRequest,
177
		MemberRequest $memberRequest,
178
		RemoteService $remoteService,
179
		RemoteStreamService $remoteStreamService,
180
		ContactService $contactService,
181
		InterfaceService $interfaceService,
182
		ConfigService $configService
183
	) {
184
		$this->userSession = $userSession;
185
		$this->userManager = $userManager;
186
		$this->groupManager = $groupManager;
187
		$this->federatedEventService = $federatedEventService;
188
		$this->membershipService = $membershipService;
189
		$this->circleRequest = $circleRequest;
190
		$this->memberRequest = $memberRequest;
191
		$this->remoteService = $remoteService;
192
		$this->remoteStreamService = $remoteStreamService;
193
		$this->contactService = $contactService;
194
		$this->interfaceService = $interfaceService;
195
		$this->configService = $configService;
196
197
		if (OC::$CLI) {
198
			$this->setInitiatedByOcc(true);
199
		}
200
	}
201
202
203
	/**
204
	 * @throws FederatedUserException
205
	 * @throws FederatedUserNotFoundException
206
	 * @throws InvalidIdException
207
	 * @throws SingleCircleNotFoundException
208
	 * @throws RequestBuilderException
209
	 */
210
	public function initCurrentUser() {
211
		$user = $this->userSession->getUser();
212
		if ($user === null) {
213
			return;
214
		}
215
216
		$this->setLocalCurrentUser($user);
217
	}
218
219
220
	/**
221
	 * @param IUser|null $user
222
	 *
223
	 * @throws FederatedUserException
224
	 * @throws FederatedUserNotFoundException
225
	 * @throws InvalidIdException
226
	 * @throws SingleCircleNotFoundException
227
	 * @throws RequestBuilderException
228
	 */
229
	public function setLocalCurrentUser(?IUser $user): void {
230
		if ($user === null) {
231
			return;
232
		}
233
234
		$this->setLocalCurrentUserId($user->getUID());
235
	}
236
237
	/**
238
	 * @param string $userId
239
	 *
240
	 * @throws FederatedUserException
241
	 * @throws FederatedUserNotFoundException
242
	 * @throws InvalidIdException
243
	 * @throws RequestBuilderException
244
	 * @throws SingleCircleNotFoundException
245
	 */
246
	public function setLocalCurrentUserId(string $userId): void {
247
		$this->currentUser = $this->getLocalFederatedUser($userId);
248
	}
249
250
	/**
251
	 * @param string $appId
252
	 * @param int $appNumber
253
	 *
254
	 * @throws FederatedUserException
255
	 * @throws InvalidIdException
256
	 * @throws SingleCircleNotFoundException
257
	 */
258
	public function setLocalCurrentApp(string $appId, int $appNumber): void {
259
		$this->currentApp = $this->getAppInitiator($appId, $appNumber);
260
	}
261
262
263
	/**
264
	 * set a CurrentUser, based on a IFederatedUser.
265
	 * CurrentUser is mainly used to manage rights when requesting the database.
266
	 *
267
	 * @param IFederatedUser $federatedUser
268
	 *
269
	 * @throws FederatedUserException
270
	 */
271
	public function setCurrentUser(IFederatedUser $federatedUser): void {
272
		if (!($federatedUser instanceof FederatedUser)) {
273
			$tmp = new FederatedUser();
274
			$tmp->importFromIFederatedUser($federatedUser);
275
			$federatedUser = $tmp;
276
		}
277
278
		$this->confirmFederatedUser($federatedUser);
279
280
		$this->currentUser = $federatedUser;
281
	}
282
283
	/**
284
	 *
285
	 */
286
	public function unsetCurrentUser(): void {
287
		$this->currentUser = null;
288
	}
289
290
	/**
291
	 * @return FederatedUser|null
292
	 */
293
	public function getCurrentUser(): ?FederatedUser {
294
		return $this->currentUser;
295
	}
296
297
	/**
298
	 * @return bool
299
	 */
300
	public function hasCurrentUser(): bool {
301
		return !is_null($this->currentUser);
302
	}
303
304
	/**
305
	 * @throws InitiatorNotFoundException
306
	 */
307
	public function mustHaveCurrentUser(): void {
308
		if ($this->bypass) {
309
			return;
310
		}
311
		if (!$this->hasCurrentUser() && !$this->hasRemoteInstance()) {
312
			throw new InitiatorNotFoundException('Invalid initiator');
313
		}
314
	}
315
316
	/**
317
	 * @param bool $bypass
318
	 */
319
	public function bypassCurrentUserCondition(bool $bypass): void {
320
		$this->bypass = $bypass;
321
	}
322
323
324
	/**
325
	 * @param bool $initiatedByOcc
326
	 */
327
	public function setInitiatedByOcc(bool $initiatedByOcc): void {
328
		$this->initiatedByOcc = $initiatedByOcc;
329
	}
330
331
	/**
332
	 * @return bool
333
	 */
334
	public function isInitiatedByOcc(): bool {
335
		return $this->initiatedByOcc;
336
	}
337
338
	/**
339
	 * @return bool
340
	 */
341
	public function isInitiatedByAdmin(): bool {
342
		return !is_null($this->initiatedByAdmin);
343
	}
344
345
	/**
346
	 * @param FederatedUser $patron
347
	 */
348
	public function setInitiatedByAdmin(FederatedUser $patron): void {
349
		$this->initiatedByAdmin = $patron;
350
	}
351
352
	/**
353
	 * @return FederatedUser
354
	 */
355
	public function getInitiatedByAdmin(): FederatedUser {
356
		return $this->initiatedByAdmin;
357
	}
358
359
	/**
360
	 * @param IUser $user
0 ignored issues
show
Bug introduced by
There is no parameter named $user. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
361
	 *
362
	 * @throws ContactAddressBookNotFoundException
363
	 * @throws ContactFormatException
364
	 * @throws ContactNotFoundException
365
	 * @throws FederatedUserException
366
	 * @throws FederatedUserNotFoundException
367
	 * @throws InvalidIdException
368
	 * @throws RequestBuilderException
369
	 * @throws SingleCircleNotFoundException
370
	 */
371
	public function setCurrentPatron(string $userId): void {
372
		$patron = $this->getLocalFederatedUser($userId, false);
373
374
		$this->setInitiatedByAdmin($patron);
375
	}
376
377
378
	/**
379
	 * @param Member $member
380
	 *
381
	 * @throws ContactAddressBookNotFoundException
382
	 * @throws ContactFormatException
383
	 * @throws ContactNotFoundException
384
	 * @throws FederatedUserException
385
	 * @throws InvalidIdException
386
	 * @throws RequestBuilderException
387
	 * @throws SingleCircleNotFoundException
388
	 */
389
	public function setMemberPatron(Member $member): void {
390
		if ($this->isInitiatedByOcc()) {
391
			$member->setInvitedBy($this->getAppInitiator('occ', Member::APP_OCC));
392
		} else if ($this->isInitiatedByAdmin()) {
393
			$member->setInvitedBy($this->getInitiatedByAdmin());
394
		} else {
395
			$member->setInvitedBy($this->getCurrentUser());
0 ignored issues
show
Bug introduced by
It seems like $this->getCurrentUser() can be null; however, setInvitedBy() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
396
		}
397
	}
398
399
400
	/**
401
	 * @return FederatedUser|null
402
	 */
403
	public function getCurrentApp(): ?FederatedUser {
404
		return $this->currentApp;
405
	}
406
407
	/**
408
	 * @return bool
409
	 */
410
	public function hasCurrentApp(): bool {
411
		return !is_null($this->currentApp);
412
	}
413
414
415
	/**
416
	 * set a RemoteInstance, mostly from a remote request (RemoteController)
417
	 * Used to limit rights in some request in the local database.
418
	 *
419
	 * @param RemoteInstance $remoteInstance
420
	 */
421
	public function setRemoteInstance(RemoteInstance $remoteInstance): void {
422
		$this->remoteInstance = $remoteInstance;
423
	}
424
425
	/**
426
	 * @return RemoteInstance|null
427
	 */
428
	public function getRemoteInstance(): ?RemoteInstance {
429
		return $this->remoteInstance;
430
	}
431
432
	/**
433
	 * @return bool
434
	 */
435
	public function hasRemoteInstance(): bool {
436
		return !is_null($this->remoteInstance);
437
	}
438
439
440
	/**
441
	 * Get the full FederatedUser for a local user.
442
	 * Will generate the SingleId if none exist
443
	 *
444
	 * @param string $userId
445
	 * @param bool $check
446
	 *
447
	 * @return FederatedUser
448
	 * @throws ContactAddressBookNotFoundException
449
	 * @throws ContactFormatException
450
	 * @throws ContactNotFoundException
451
	 * @throws FederatedUserException
452
	 * @throws FederatedUserNotFoundException
453
	 * @throws InvalidIdException
454
	 * @throws RequestBuilderException
455
	 * @throws SingleCircleNotFoundException
456
	 */
457
	public function getLocalFederatedUser(string $userId, bool $check = true): FederatedUser {
458
		$displayName = $userId;
459
		if ($check) {
460
			$user = $this->userManager->get($userId);
461
			if ($user === null) {
462
				throw new FederatedUserNotFoundException('user ' . $userId . ' not found');
463
			}
464
			$userId = $user->getUID();
465
			$displayName = $user->getDisplayName();
466
		}
467
468
		$federatedUser = new FederatedUser();
469
		$federatedUser->set($userId, '', Member::TYPE_USER, $displayName);
470
		$this->fillSingleCircleId($federatedUser, $check);
471
472
		return $federatedUser;
473
	}
474
475
476
	/**
477
	 * Get the full FederatedUser for a local user.
478
	 * Will generate the SingleId if none exist
479
	 *
480
	 * @param string $appId
481
	 * @param int $appNumber
482
	 *
483
	 * @return FederatedUser
484
	 * @throws ContactAddressBookNotFoundException
485
	 * @throws ContactFormatException
486
	 * @throws ContactNotFoundException
487
	 * @throws FederatedUserException
488
	 * @throws InvalidIdException
489
	 * @throws RequestBuilderException
490
	 * @throws SingleCircleNotFoundException
491
	 */
492
	public function getAppInitiator(
493
		string $appId,
494
		int $appNumber,
495
		string $appDisplayName = ''
496
	): FederatedUser {
497
		if ($appDisplayName === '') {
498
			$appDisplayName = $this->get((string)$appNumber, Circle::$DEF_SOURCE, $appId);
499
		}
500
501
		$circle = new Circle();
502
		$circle->setSource($appNumber);
503
504
		$federatedUser = new FederatedUser();
505
		$federatedUser->set($appId, '', Member::TYPE_APP, $appDisplayName, $circle);
506
507
		$this->fillSingleCircleId($federatedUser);
508
509
		return $federatedUser;
510
	}
511
512
513
	/**
514
	 * some ./occ commands allows to add an Initiator, or force the PoV from the local circles' owner
515
	 *
516
	 * TODO: manage non-user type ?
517
	 *
518
	 * @param string $userId
519
	 * @param int $userType
520
	 * @param string $circleId
521
	 * @param bool $bypass
522
	 *
523
	 * @throws CircleNotFoundException
524
	 * @throws FederatedItemException
525
	 * @throws FederatedUserException
526
	 * @throws FederatedUserNotFoundException
527
	 * @throws InvalidIdException
528
	 * @throws MemberNotFoundException
529
	 * @throws OwnerNotFoundException
530
	 * @throws RemoteInstanceException
531
	 * @throws RemoteNotFoundException
532
	 * @throws RemoteResourceNotFoundException
533
	 * @throws RequestBuilderException
534
	 * @throws SingleCircleNotFoundException
535
	 * @throws UnknownRemoteException
536
	 * @throws UserTypeNotFoundException
537
	 */
538
	public function commandLineInitiator(
539
		string $userId,
540
		int $userType = Member::TYPE_SINGLE,
541
		string $circleId = '',
542
		bool $bypass = false
543
	): void {
544
		if ($userId !== '') {
545
			$this->setCurrentUser($this->getFederatedUser($userId, $userType));
546
547
			return;
548
		}
549
550
		if ($circleId !== '') {
551
			$localCircle = $this->circleRequest->getCircle($circleId, null, null, 0);
552
			if ($this->configService->isLocalInstance($localCircle->getInstance())) {
553
				$this->setCurrentUser($localCircle->getOwner());
554
555
				return;
556
			}
557
		}
558
559
		if (!$bypass) {
560
			throw new CircleNotFoundException(
561
				'This Circle is not managed from this instance, please use --initiator'
562
			);
563
		}
564
565
		$this->bypassCurrentUserCondition($bypass);
566
	}
567
568
569
	/**
570
	 * Works like getFederatedUser, but returns a member.
571
	 * Allow to specify a level: handle@instance,level
572
	 *
573
	 * Used for filters when searching for Circles
574
	 * TODO: Used outside of ./occ circles:manage:list ?
575
	 *
576
	 * @param string $userId
577
	 * @param int $level
578
	 *
579
	 * @return Member
580
	 * @throws CircleNotFoundException
581
	 * @throws FederatedItemException
582
	 * @throws FederatedUserException
583
	 * @throws FederatedUserNotFoundException
584
	 * @throws InvalidIdException
585
	 * @throws MemberNotFoundException
586
	 * @throws OwnerNotFoundException
587
	 * @throws RemoteInstanceException
588
	 * @throws RemoteNotFoundException
589
	 * @throws RemoteResourceNotFoundException
590
	 * @throws SingleCircleNotFoundException
591
	 * @throws UnknownRemoteException
592
	 * @throws UserTypeNotFoundException
593
	 */
594
	public function getFederatedMember(string $userId, int $level = Member::LEVEL_MEMBER): Member {
595
		$userId = trim($userId, ',');
596
		if (strpos($userId, ',') !== false) {
597
			list($userId, $level) = explode(',', $userId);
598
		}
599
600
		$federatedUser = $this->getFederatedUser($userId, Member::TYPE_USER);
601
		$member = new Member();
602
		$member->importFromIFederatedUser($federatedUser);
603
		$member->setLevel((int)$level);
604
605
		return $member;
606
	}
607
608
609
	/**
610
	 * get a valid FederatedUser, based on the federatedId (userId@instance) and its type.
611
	 * If instance is local, get the local valid FederatedUser
612
	 * If instance is not local, get the remote valid FederatedUser
613
	 *
614
	 * @param string $federatedId
615
	 * @param int $type
616
	 *
617
	 * @return FederatedUser
618
	 * @throws CircleNotFoundException
619
	 * @throws FederatedItemException
620
	 * @throws FederatedUserException
621
	 * @throws FederatedUserNotFoundException
622
	 * @throws InvalidIdException
623
	 * @throws MemberNotFoundException
624
	 * @throws OwnerNotFoundException
625
	 * @throws RemoteInstanceException
626
	 * @throws RemoteNotFoundException
627
	 * @throws RemoteResourceNotFoundException
628
	 * @throws SingleCircleNotFoundException
629
	 * @throws UnknownRemoteException
630
	 * @throws UserTypeNotFoundException
631
	 * @throws RequestBuilderException
632
	 */
633
	public function getFederatedUser(string $federatedId, int $type = Member::TYPE_SINGLE): FederatedUser {
634
		// if type=user, we check that handle@domain is not an actual local user
635
		if ($type === Member::TYPE_USER) {
636
			try {
637
				return $this->getLocalFederatedUser($federatedId);
638
			} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
639
			}
640
		}
641
642
		list($singleId, $instance) = $this->extractIdAndInstance($federatedId);
643
644
		switch ($type) {
645
			case Member::TYPE_SINGLE:
646
			case Member::TYPE_CIRCLE:
647
				return $this->getFederatedUser_SingleId($singleId, $instance);
648
			case Member::TYPE_USER:
649
				return $this->getFederatedUser_User($singleId, $instance);
650
			case Member::TYPE_GROUP:
651
				return $this->getFederatedUser_Group($singleId, $instance);
652
			case Member::TYPE_MAIL:
653
				return $this->getFederatedUser_Mail($federatedId);
654
			case Member::TYPE_CONTACT:
655
				return $this->getFederatedUser_Contact($federatedId);
656
		}
657
658
		throw new UserTypeNotFoundException();
659
	}
660
661
662
	/**
663
	 * Generate a FederatedUser based on local data.
664
	 * WARNING: There is no confirmation that the returned FederatedUser exists or is valid at this point.
665
	 * Use getFederatedUser() instead if a valid and confirmed FederatedUser is needed.
666
	 *
667
	 * if $federatedId is a known SingleId, will returns data from the local database.
668
	 * if $federatedId is a local username, will returns data from the local database.
669
	 * Otherwise, the FederatedUser will not contains a SingleId.
670
	 *
671
	 * @param string $federatedId
672
	 * @param int $type
673
	 *
674
	 * @return FederatedUser
675
	 */
676
	public function generateFederatedUser(string $federatedId, int $type = 0): FederatedUser {
677
		try {
678
			return $this->getFederatedUser($federatedId, $type);
679
		} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
680
		}
681
682
		list($userId, $instance) = $this->extractIdAndInstance($federatedId);
683
		$federatedUser = new FederatedUser();
684
		$federatedUser->set($userId, $instance, $type);
685
686
		return $federatedUser;
687
	}
688
689
690
	/**
691
	 * @param FederatedUser $federatedUser
692
	 */
693
	public function deleteFederatedUser(FederatedUser $federatedUser): void {
694
		$this->circleRequest->deleteFederatedUser($federatedUser);
695
		$this->memberRequest->deleteFederatedUser($federatedUser);
696
		$this->membershipService->deleteFederatedUser($federatedUser);
697
	}
698
699
700
	/**
701
	 * @param string $singleId
702
	 * @param string $instance
703
	 *
704
	 * @return FederatedUser
705
	 * @throws CircleNotFoundException
706
	 * @throws FederatedItemException
707
	 * @throws FederatedUserException
708
	 * @throws FederatedUserNotFoundException
709
	 * @throws MemberNotFoundException
710
	 * @throws OwnerNotFoundException
711
	 * @throws RemoteInstanceException
712
	 * @throws RemoteNotFoundException
713
	 * @throws RemoteResourceNotFoundException
714
	 * @throws UnknownRemoteException
715
	 * @throws RequestBuilderException
716
	 */
717
	public function getFederatedUser_SingleId(string $singleId, string $instance): FederatedUser {
718
		if ($this->configService->isLocalInstance($instance)) {
719
			return $this->circleRequest->getFederatedUserBySingleId($singleId);
720
		} else {
721
			$federatedUser = $this->remoteService->getFederatedUserFromInstance(
722
				$singleId,
723
				$instance,
724
				Member::TYPE_SINGLE
725
			);
726
727
			$this->confirmSingleIdUniqueness($federatedUser);
728
729
			return $federatedUser;
730
		}
731
	}
732
733
734
	/**
735
	 * @param string $userId
736
	 * @param string $instance
737
	 *
738
	 * @return FederatedUser
739
	 * @throws FederatedUserException
740
	 * @throws FederatedUserNotFoundException
741
	 * @throws InvalidIdException
742
	 * @throws RemoteInstanceException
743
	 * @throws RemoteNotFoundException
744
	 * @throws RemoteResourceNotFoundException
745
	 * @throws SingleCircleNotFoundException
746
	 * @throws UnknownRemoteException
747
	 * @throws FederatedItemException
748
	 * @throws RequestBuilderException
749
	 */
750
	private function getFederatedUser_User(string $userId, string $instance): FederatedUser {
751
		if ($this->configService->isLocalInstance($instance)) {
752
			return $this->getLocalFederatedUser($userId);
753
		} else {
754
			$federatedUser = $this->remoteService->getFederatedUserFromInstance(
755
				$userId,
756
				$instance,
757
				Member::TYPE_USER
758
			);
759
760
			$this->confirmSingleIdUniqueness($federatedUser);
761
762
			return $federatedUser;
763
		}
764
	}
765
766
767
	/**
768
	 * @param string $groupName
769
	 * @param string $instance
770
	 *
771
	 * @return FederatedUser
772
	 * @throws FederatedEventException
773
	 * @throws FederatedItemException
774
	 * @throws FederatedUserException
775
	 * @throws GroupNotFoundException
776
	 * @throws InitiatorNotConfirmedException
777
	 * @throws InvalidIdException
778
	 * @throws OwnerNotFoundException
779
	 * @throws RemoteInstanceException
780
	 * @throws RemoteNotFoundException
781
	 * @throws RemoteResourceNotFoundException
782
	 * @throws SingleCircleNotFoundException
783
	 * @throws UnknownRemoteException
784
	 * @throws RequestBuilderException
785
	 */
786
	public function getFederatedUser_Group(string $groupName, string $instance): FederatedUser {
787
		if ($this->configService->isLocalInstance($instance)) {
788
			$circle = $this->getGroupCircle($groupName);
789
			$federatedGroup = new FederatedUser();
790
791
			return $federatedGroup->importFromCircle($circle);
792
		} else {
793
			// TODO: implement remote groups
794
		}
795
	}
796
797
798
	/**
799
	 * @param string $mailAddress
800
	 *
801
	 * @return FederatedUser
802
	 * @throws ContactAddressBookNotFoundException
803
	 * @throws ContactFormatException
804
	 * @throws ContactNotFoundException
805
	 * @throws FederatedUserException
806
	 * @throws InvalidIdException
807
	 * @throws RequestBuilderException
808
	 * @throws SingleCircleNotFoundException
809
	 */
810
	public function getFederatedUser_Mail(string $mailAddress): FederatedUser {
811
		$federatedUser = new FederatedUser();
812
		$federatedUser->set($mailAddress, '', Member::TYPE_MAIL);
813
		$this->fillSingleCircleId($federatedUser);
814
815
		return $federatedUser;
816
	}
817
818
819
	/**
820
	 * @param string $contactPath
821
	 *
822
	 * @return FederatedUser
823
	 * @throws ContactAddressBookNotFoundException
824
	 * @throws ContactFormatException
825
	 * @throws ContactNotFoundException
826
	 * @throws FederatedUserException
827
	 * @throws InvalidIdException
828
	 * @throws RequestBuilderException
829
	 * @throws SingleCircleNotFoundException
830
	 */
831
	public function getFederatedUser_Contact(string $contactPath): FederatedUser {
832
		$federatedUser = new FederatedUser();
833
		$federatedUser->set(
834
			$contactPath,
835
			'',
836
			Member::TYPE_CONTACT,
837
			$this->contactService->getDisplayName($contactPath)
838
		);
839
840
		$this->fillSingleCircleId($federatedUser);
841
842
		return $federatedUser;
843
	}
844
845
846
	/**
847
	 * extract userID and instance from a federatedId
848
	 *
849
	 * @param string $federatedId
850
	 *
851
	 * @return array
852
	 */
853
	public function extractIdAndInstance(string $federatedId): array {
854
		$federatedId = trim($federatedId, '@');
855
		if (strrpos($federatedId, '@') === false) {
856
			$userId = $federatedId;
857
			$instance = $this->interfaceService->getLocalInstance();
858
		} else {
859
			list($userId, $instance) = explode('@', $federatedId);
860
		}
861
862
		return [$userId, $instance];
863
	}
864
865
866
	/**
867
	 * @param FederatedUser $federatedUser
868
	 * @param bool $generate
869
	 *
870
	 * @throws ContactAddressBookNotFoundException
871
	 * @throws ContactFormatException
872
	 * @throws ContactNotFoundException
873
	 * @throws FederatedUserException
874
	 * @throws InvalidIdException
875
	 * @throws RequestBuilderException
876
	 * @throws SingleCircleNotFoundException
877
	 */
878
	private function fillSingleCircleId(FederatedUser $federatedUser, bool $generate = true): void {
879
		if ($federatedUser->getSingleId() !== '') {
880
			return;
881
		}
882
883
		$circle = $this->getSingleCircle($federatedUser, $generate);
884
		$federatedUser->setSingleId($circle->getSingleId());
885
		$federatedUser->setDisplayName($circle->getDisplayName());
886
		$federatedUser->setBasedOn($circle);
887
	}
888
889
890
	/**
891
	 * get the Single Circle from a local user
892
	 *
893
	 * @param FederatedUser $federatedUser
894
	 * @param bool $generate
895
	 *
896
	 * @return Circle
897
	 * @throws ContactAddressBookNotFoundException
898
	 * @throws ContactFormatException
899
	 * @throws ContactNotFoundException
900
	 * @throws FederatedUserException
901
	 * @throws InvalidIdException
902
	 * @throws RequestBuilderException
903
	 * @throws SingleCircleNotFoundException
904
	 */
905
	private function getSingleCircle(FederatedUser $federatedUser, bool $generate = true): Circle {
906
		if (!$this->configService->isLocalInstance($federatedUser->getInstance())) {
907
			throw new FederatedUserException('FederatedUser must be local');
908
		}
909
910
		try {
911
			return $this->circleRequest->getSingleCircle($federatedUser);
912
		} catch (SingleCircleNotFoundException $e) {
913
			if (!$generate) {
914
				throw new SingleCircleNotFoundException();
915
			}
916
917
			$circle = new Circle();
918
			$id = $this->token(ManagedModel::ID_LENGTH);
919
920
			if ($federatedUser->hasBasedOn()) {
921
				$source = $federatedUser->getBasedOn()->getSource();
922
			} else {
923
				$source = $federatedUser->getUserType();
924
			}
925
926
			$prefix = ($federatedUser->getUserType() === Member::TYPE_APP) ? 'app'
927
				: Member::$TYPE[$federatedUser->getUserType()];
928
929
			$circle->setName($prefix . ':' . $federatedUser->getUserId() . ':' . $id)
930
				   ->setDisplayName($federatedUser->getDisplayName())
931
				   ->setSingleId($id)
932
				   ->setSource($source);
933
934
			if ($federatedUser->getUserType() === Member::TYPE_APP) {
935
				$circle->setConfig(Circle::CFG_SINGLE | Circle::CFG_ROOT);
936
			} else {
937
				$circle->setConfig(Circle::CFG_SINGLE);
938
			}
939
			$this->circleRequest->save($circle);
940
941
			$owner = new Member();
942
			$owner->importFromIFederatedUser($federatedUser);
943
			$owner->setLevel(Member::LEVEL_OWNER)
944
				  ->setCircleId($id)
945
				  ->setSingleId($id)
946
				  ->setId($id)
947
				  ->setDisplayName($owner->getDisplayName())
948
				  ->setStatus('Member');
949
950
			if ($federatedUser->getUserType() !== Member::TYPE_APP) {
951
				$owner->setInvitedBy(
952
					$this->getAppInitiator(
953
						Application::APP_ID,
954
						Member::APP_CIRCLES,
955
						Application::APP_NAME
956
					)
957
				);
958
			}
959
960
			$this->memberRequest->save($owner);
961
			// TODO: should not be needed
962
			// $this->membershipService->onUpdate($id);
963
		}
964
965
		return $this->circleRequest->getSingleCircle($federatedUser);
966
	}
967
968
969
	/**
970
	 * Confirm that all field of a FederatedUser are filled.
971
	 *
972
	 * @param FederatedUser $federatedUser
973
	 *
974
	 * @throws FederatedUserException
975
	 */
976
	private function confirmFederatedUser(FederatedUser $federatedUser): void {
977
		if ($federatedUser->getUserId() === ''
978
			|| $federatedUser->getSingleId() === ''
979
			|| $federatedUser->getUserType() === 0
980
			|| $federatedUser->getInstance() === '') {
981
			$this->debug('FederatedUser is not empty', ['federatedUser' => $federatedUser]);
982
			throw new FederatedUserException('FederatedUser is not complete');
983
		}
984
	}
985
986
	/**
987
	 * Confirm that the singleId of a FederatedUser is unique and not used to any other member of the
988
	 * database.
989
	 *
990
	 * @param FederatedUser $federatedUser
991
	 *
992
	 * @throws FederatedUserException
993
	 * @throws RequestBuilderException
994
	 * @deprecated: use confirmSingleIdUniqueness()
995
	 */
996
	public function confirmLocalSingleId(IFederatedUser $federatedUser): void {
997
		$members = $this->memberRequest->getMembersBySingleId($federatedUser->getSingleId());
998
999
		foreach ($members as $member) {
1000
			if (!$federatedUser->compareWith($member)) {
1001
				$this->debug(
1002
					'uniqueness of SingleId could not be confirmed',
1003
					['federatedUser' => $federatedUser, 'localMember' => $member]
1004
				);
1005
				throw new FederatedUserException('uniqueness of SingleId could not be confirmed');
1006
			}
1007
		}
1008
	}
1009
1010
1011
	/**
1012
	 * // TODO: implement this check in a maintenance background job
1013
	 *
1014
	 * @param IFederatedUser $federatedUser
1015
	 *
1016
	 * @throws FederatedUserException
1017
	 * @throws RemoteNotFoundException
1018
	 * @throws RequestBuilderException
1019
	 * @throws UnknownRemoteException
1020
	 */
1021
	public function confirmSingleIdUniqueness(IFederatedUser $federatedUser): void {
1022
		// TODO: check also with Circles singleId
1023
1024
		$remote = null;
1025
		if (!$this->configService->isLocalInstance($federatedUser->getInstance())) {
1026
			$remote = $this->remoteStreamService->getCachedRemoteInstance($federatedUser->getInstance());
1027
		}
1028
1029
		$knownMembers = $this->memberRequest->getAlternateSingleId($federatedUser);
1030
		foreach ($knownMembers as $knownMember) {
1031
			if ($this->configService->isLocalInstance($federatedUser->getInstance())) {
1032
				if ($this->configService->isLocalInstance($knownMember->getInstance())) {
1033
					return;
1034
				} else {
1035
					$this->markConflict($federatedUser, $knownMember, self::CONFLICT_001);
1036
				}
1037
			}
1038
1039
			if (!$knownMember->hasRemoteInstance()) {
1040
				$this->markConflict($federatedUser, $knownMember, self::CONFLICT_002);
1041
			}
1042
1043
			$knownRemote = $knownMember->getRemoteInstance();
1044
			if ($this->interfaceService->isInterfaceInternal($knownRemote->getInterface())
1045
				&& !in_array($federatedUser->getInstance(), $knownRemote->getAliases())) {
1046
				$this->markConflict($federatedUser, $knownMember, self::CONFLICT_003);
1047
			}
1048
1049
			if (is_null($remote)) {
1050
				$this->markConflict($federatedUser, $knownMember, self::CONFLICT_004);
1051
			}
1052
1053
			if ($this->interfaceService->isInterfaceInternal($remote->getInterface())
1054
				&& !in_array($knownMember->getInstance(), $remote->getAliases())) {
1055
				$this->markConflict($federatedUser, $knownMember, self::CONFLICT_005);
1056
			}
1057
		}
1058
	}
1059
1060
1061
	/**
1062
	 * @param IFederatedUser $federatedUser
1063
	 * @param Member $knownMember
1064
	 * @param int $conflict
1065
	 *
1066
	 * @throws FederatedUserException
1067
	 */
1068
	private function markConflict(IFederatedUser $federatedUser, Member $knownMember, int $conflict): void {
1069
		switch ($conflict) {
1070
			case self::CONFLICT_001:
1071
				$message = 'duplicate singleId from another instance';
1072
				break;
1073
			case self::CONFLICT_002:
1074
				$message = 'duplicate singleId has no known source';
1075
				break;
1076
			case self::CONFLICT_003:
1077
				$message = 'federatedUser is not an alias from duplicate singleId';
1078
				break;
1079
			case self::CONFLICT_004:
1080
				$message = 'federatedUser has no known source';
1081
				break;
1082
			case self::CONFLICT_005:
1083
				$message = 'duplicate singleId is not an alias of federatedUser';
1084
				break;
1085
1086
			default:
1087
				$message = 'uniqueness of SingleId could not be confirmed';
1088
		}
1089
1090
		// TODO: log conflict into database
1091
		$this->log(
1092
			3, $message, false,
1093
			[
1094
				'federatedUser' => $federatedUser,
1095
				'knownMember'   => $knownMember
1096
			]
1097
		);
1098
1099
		throw new FederatedUserException($message);
1100
	}
1101
1102
	/**
1103
	 * @param string $groupId
1104
	 *
1105
	 * @return Circle
1106
	 * @throws GroupNotFoundException
1107
	 * @throws FederatedEventException
1108
	 * @throws FederatedItemException
1109
	 * @throws FederatedUserException
1110
	 * @throws InitiatorNotConfirmedException
1111
	 * @throws InvalidIdException
1112
	 * @throws OwnerNotFoundException
1113
	 * @throws RemoteInstanceException
1114
	 * @throws RemoteNotFoundException
1115
	 * @throws RemoteResourceNotFoundException
1116
	 * @throws SingleCircleNotFoundException
1117
	 * @throws UnknownRemoteException
1118
	 * @throws RequestBuilderException
1119
	 */
1120
	public function getGroupCircle(string $groupId): Circle {
1121
		$group = $this->groupManager->get($groupId);
1122
		if ($group === null) {
1123
			throw new GroupNotFoundException('group not found');
1124
		}
1125
1126
		$this->setLocalCurrentApp(Application::APP_ID, Member::APP_CIRCLES);
1127
		$owner = $this->getCurrentApp();
1128
1129
		$circle = new Circle();
1130
		$circle->setName('group:' . $groupId)
1131
			   ->setConfig(Circle::CFG_SYSTEM | Circle::CFG_NO_OWNER | Circle::CFG_HIDDEN)
1132
			   ->setSingleId($this->token(ManagedModel::ID_LENGTH))
1133
			   ->setSource(Member::TYPE_GROUP);
1134
1135
		$member = new Member();
1136
		$member->importFromIFederatedUser($owner);
0 ignored issues
show
Bug introduced by
It seems like $owner defined by $this->getCurrentApp() on line 1127 can be null; however, OCA\Circles\Model\Manage...ortFromIFederatedUser() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1137
		$member->setId($this->token(ManagedModel::ID_LENGTH))
1138
			   ->setCircleId($circle->getSingleId())
1139
			   ->setLevel(Member::LEVEL_OWNER)
1140
			   ->setStatus(Member::STATUS_MEMBER);
1141
		$circle->setOwner($member)
1142
			   ->setInitiator($member);
1143
1144
		try {
1145
			return $this->circleRequest->searchCircle($circle, $owner);
1146
		} catch (CircleNotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1147
		}
1148
1149
		$circle->setDisplayName($groupId);
1150
1151
		$event = new FederatedEvent(CircleCreate::class);
1152
		$event->setCircle($circle);
1153
		$this->federatedEventService->newEvent($event);
1154
1155
		return $circle;
1156
	}
1157
1158
}
1159
1160