Completed
Push — master ( 98fb41...18d3f8 )
by Maxence
02:48 queued 10s
created

FederatedUserService::confirmSingleIdUniqueness()   B

Complexity

Conditions 11
Paths 16

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 40
rs 7.3166
c 0
b 0
f 0
cc 11
nc 16
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 daita\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Logger;
36
use daita\MySmallPhpTools\Traits\TArrayTools;
37
use daita\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
	/** @var IUserSession */
92
	private $userSession;
93
94
	/** @var IUserManager */
95
	private $userManager;
96
97
	/** @var IGroupManager */
98
	private $groupManager;
99
100
	/** @var FederatedEventService */
101
	private $federatedEventService;
102
103
	/** @var MembershipService */
104
	private $membershipService;
105
106
	/** @var CircleRequest */
107
	private $circleRequest;
108
109
	/** @var MemberRequest */
110
	private $memberRequest;
111
112
	/** @var RemoteService */
113
	private $remoteService;
114
115
	/** @var RemoteStreamService */
116
	private $remoteStreamService;
117
118
	/** @var ContactService */
119
	private $contactService;
120
121
	/** @var InterfaceService */
122
	private $interfaceService;
123
124
	/** @var ConfigService */
125
	private $configService;
126
127
128
	/** @var FederatedUser */
129
	private $currentUser = null;
130
131
	/** @var FederatedUser */
132
	private $currentApp = null;
133
134
	/** @var RemoteInstance */
135
	private $remoteInstance = null;
136
137
	/** @var bool */
138
	private $bypass = false;
139
140
	/** @var bool */
141
	private $initiatedByOcc = false;
142
143
144
	/**
145
	 * FederatedUserService constructor.
146
	 *
147
	 * @param IUserSession $userSession
148
	 * @param IUserManager $userManager
149
	 * @param IGroupManager $groupManager
150
	 * @param FederatedEventService $federatedEventService
151
	 * @param MembershipService $membershipService
152
	 * @param CircleRequest $circleRequest
153
	 * @param MemberRequest $memberRequest
154
	 * @param RemoteService $remoteService
155
	 * @param RemoteStreamService $remoteStreamService
156
	 * @param ContactService $contactService
157
	 * @param InterfaceService $interfaceService
158
	 * @param ConfigService $configService
159
	 */
160
	public function __construct(
161
		IUserSession $userSession,
162
		IUserManager $userManager,
163
		IGroupManager $groupManager,
164
		FederatedEventService $federatedEventService,
165
		MembershipService $membershipService,
166
		CircleRequest $circleRequest,
167
		MemberRequest $memberRequest,
168
		RemoteService $remoteService,
169
		RemoteStreamService $remoteStreamService,
170
		ContactService $contactService,
171
		InterfaceService $interfaceService,
172
		ConfigService $configService
173
	) {
174
		$this->userSession = $userSession;
175
		$this->userManager = $userManager;
176
		$this->groupManager = $groupManager;
177
		$this->federatedEventService = $federatedEventService;
178
		$this->membershipService = $membershipService;
179
		$this->circleRequest = $circleRequest;
180
		$this->memberRequest = $memberRequest;
181
		$this->remoteService = $remoteService;
182
		$this->remoteStreamService = $remoteStreamService;
183
		$this->contactService = $contactService;
184
		$this->interfaceService = $interfaceService;
185
		$this->configService = $configService;
186
187
		if (OC::$CLI) {
188
			$this->setInitiatedByOcc(true);
189
		}
190
	}
191
192
193
	/**
194
	 * @throws FederatedUserException
195
	 * @throws FederatedUserNotFoundException
196
	 * @throws InvalidIdException
197
	 * @throws SingleCircleNotFoundException
198
	 * @throws RequestBuilderException
199
	 */
200
	public function initCurrentUser() {
201
		$user = $this->userSession->getUser();
202
		if ($user === null) {
203
			return;
204
		}
205
206
		$this->setLocalCurrentUser($user);
207
	}
208
209
210
	/**
211
	 * @param IUser|null $user
212
	 *
213
	 * @throws FederatedUserException
214
	 * @throws FederatedUserNotFoundException
215
	 * @throws InvalidIdException
216
	 * @throws SingleCircleNotFoundException
217
	 * @throws RequestBuilderException
218
	 */
219
	public function setLocalCurrentUser(?IUser $user): void {
220
		if ($user === null) {
221
			return;
222
		}
223
224
		$this->setLocalCurrentUserId($user->getUID());
225
	}
226
227
	/**
228
	 * @param string $userId
229
	 *
230
	 * @throws FederatedUserException
231
	 * @throws FederatedUserNotFoundException
232
	 * @throws InvalidIdException
233
	 * @throws RequestBuilderException
234
	 * @throws SingleCircleNotFoundException
235
	 */
236
	public function setLocalCurrentUserId(string $userId): void {
237
		$this->currentUser = $this->getLocalFederatedUser($userId);
238
	}
239
240
	/**
241
	 * @param string $appId
242
	 * @param int $appNumber
243
	 *
244
	 * @throws FederatedUserException
245
	 * @throws InvalidIdException
246
	 * @throws SingleCircleNotFoundException
247
	 */
248
	public function setLocalCurrentApp(string $appId, int $appNumber): void {
249
		$this->currentApp = $this->getAppInitiator($appId, $appNumber);
250
	}
251
252
253
	/**
254
	 * set a CurrentUser, based on a IFederatedUser.
255
	 * CurrentUser is mainly used to manage rights when requesting the database.
256
	 *
257
	 * @param IFederatedUser $federatedUser
258
	 *
259
	 * @throws FederatedUserException
260
	 */
261
	public function setCurrentUser(IFederatedUser $federatedUser): void {
262
		if (!($federatedUser instanceof FederatedUser)) {
263
			$tmp = new FederatedUser();
264
			$tmp->importFromIFederatedUser($federatedUser);
265
			$federatedUser = $tmp;
266
		}
267
268
		$this->confirmFederatedUser($federatedUser);
269
270
		$this->currentUser = $federatedUser;
271
	}
272
273
	/**
274
	 *
275
	 */
276
	public function unsetCurrentUser(): void {
277
		$this->currentUser = null;
278
	}
279
280
	/**
281
	 * @return FederatedUser|null
282
	 */
283
	public function getCurrentUser(): ?FederatedUser {
284
		return $this->currentUser;
285
	}
286
287
	/**
288
	 * @return bool
289
	 */
290
	public function hasCurrentUser(): bool {
291
		return !is_null($this->currentUser);
292
	}
293
294
	/**
295
	 * @throws InitiatorNotFoundException
296
	 */
297
	public function mustHaveCurrentUser(): void {
298
		if ($this->bypass) {
299
			return;
300
		}
301
		if (!$this->hasCurrentUser() && !$this->hasRemoteInstance()) {
302
			throw new InitiatorNotFoundException('Invalid initiator');
303
		}
304
	}
305
306
	/**
307
	 * @param bool $bypass
308
	 */
309
	public function bypassCurrentUserCondition(bool $bypass): void {
310
		$this->bypass = $bypass;
311
	}
312
313
314
	/**
315
	 * @param bool $initiatedByOcc
316
	 */
317
	public function setInitiatedByOcc(bool $initiatedByOcc): void {
318
		$this->initiatedByOcc = $initiatedByOcc;
319
	}
320
321
	/**
322
	 * @return bool
323
	 */
324
	public function isInitiatedByOcc(): bool {
325
		return $this->initiatedByOcc;
326
	}
327
328
	/**
329
	 * @param Member $member
330
	 *
331
	 * @throws ContactAddressBookNotFoundException
332
	 * @throws ContactFormatException
333
	 * @throws ContactNotFoundException
334
	 * @throws FederatedUserException
335
	 * @throws InvalidIdException
336
	 * @throws RequestBuilderException
337
	 * @throws SingleCircleNotFoundException
338
	 */
339
	public function setMemberPatron(Member $member): void {
340
		if ($this->isInitiatedByOcc()) {
341
			$member->setInvitedBy($this->getAppInitiator('occ', Member::APP_OCC));
342
		} else {
343
			$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...
344
		}
345
	}
346
347
348
	/**
349
	 * @return FederatedUser|null
350
	 */
351
	public function getCurrentApp(): ?FederatedUser {
352
		return $this->currentApp;
353
	}
354
355
	/**
356
	 * @return bool
357
	 */
358
	public function hasCurrentApp(): bool {
359
		return !is_null($this->currentApp);
360
	}
361
362
363
	/**
364
	 * set a RemoteInstance, mostly from a remote request (RemoteController)
365
	 * Used to limit rights in some request in the local database.
366
	 *
367
	 * @param RemoteInstance $remoteInstance
368
	 */
369
	public function setRemoteInstance(RemoteInstance $remoteInstance): void {
370
		$this->remoteInstance = $remoteInstance;
371
	}
372
373
	/**
374
	 * @return RemoteInstance|null
375
	 */
376
	public function getRemoteInstance(): ?RemoteInstance {
377
		return $this->remoteInstance;
378
	}
379
380
	/**
381
	 * @return bool
382
	 */
383
	public function hasRemoteInstance(): bool {
384
		return !is_null($this->remoteInstance);
385
	}
386
387
388
	/**
389
	 * Get the full FederatedUser for a local user.
390
	 * Will generate the SingleId if none exist
391
	 *
392
	 * @param string $userId
393
	 * @param bool $check
394
	 *
395
	 * @return FederatedUser
396
	 * @throws ContactAddressBookNotFoundException
397
	 * @throws ContactFormatException
398
	 * @throws ContactNotFoundException
399
	 * @throws FederatedUserException
400
	 * @throws FederatedUserNotFoundException
401
	 * @throws InvalidIdException
402
	 * @throws RequestBuilderException
403
	 * @throws SingleCircleNotFoundException
404
	 */
405
	public function getLocalFederatedUser(string $userId, bool $check = true): FederatedUser {
406
		$displayName = $userId;
407
		if ($check) {
408
			$user = $this->userManager->get($userId);
409
			if ($user === null) {
410
				throw new FederatedUserNotFoundException('user ' . $userId . ' not found');
411
			}
412
			$userId = $user->getUID();
413
			$displayName = $user->getDisplayName();
414
		}
415
416
		$federatedUser = new FederatedUser();
417
		$federatedUser->set($userId, '', Member::TYPE_USER, $displayName);
418
		$this->fillSingleCircleId($federatedUser, $check);
419
420
		return $federatedUser;
421
	}
422
423
424
	/**
425
	 * Get the full FederatedUser for a local user.
426
	 * Will generate the SingleId if none exist
427
	 *
428
	 * @param string $appId
429
	 * @param int $appNumber
430
	 *
431
	 * @return FederatedUser
432
	 * @throws ContactAddressBookNotFoundException
433
	 * @throws ContactFormatException
434
	 * @throws ContactNotFoundException
435
	 * @throws FederatedUserException
436
	 * @throws InvalidIdException
437
	 * @throws RequestBuilderException
438
	 * @throws SingleCircleNotFoundException
439
	 */
440
	public function getAppInitiator(
441
		string $appId,
442
		int $appNumber,
443
		string $appDisplayName = ''
444
	): FederatedUser {
445
446
		$appDisplayName = ($appDisplayName === '') ? $appId : $appDisplayName;
447
		$circle = new Circle();
448
		$circle->setSource($appNumber);
449
450
		$federatedUser = new FederatedUser();
451
		$federatedUser->set($appId, '', Member::TYPE_APP, $appDisplayName, $circle);
452
453
		$this->fillSingleCircleId($federatedUser);
454
455
		return $federatedUser;
456
	}
457
458
459
	/**
460
	 * some ./occ commands allows to add an Initiator, or force the PoV from the local circles' owner
461
	 *
462
	 * TODO: manage non-user type ?
463
	 *
464
	 * @param string $userId
465
	 * @param int $userType
466
	 * @param string $circleId
467
	 * @param bool $bypass
468
	 *
469
	 * @throws CircleNotFoundException
470
	 * @throws FederatedItemException
471
	 * @throws FederatedUserException
472
	 * @throws FederatedUserNotFoundException
473
	 * @throws InvalidIdException
474
	 * @throws MemberNotFoundException
475
	 * @throws OwnerNotFoundException
476
	 * @throws RemoteInstanceException
477
	 * @throws RemoteNotFoundException
478
	 * @throws RemoteResourceNotFoundException
479
	 * @throws RequestBuilderException
480
	 * @throws SingleCircleNotFoundException
481
	 * @throws UnknownRemoteException
482
	 * @throws UserTypeNotFoundException
483
	 */
484
	public function commandLineInitiator(
485
		string $userId,
486
		int $userType = Member::TYPE_SINGLE,
487
		string $circleId = '',
488
		bool $bypass = false
489
	): void {
490
		if ($userId !== '') {
491
			$this->setCurrentUser($this->getFederatedUser($userId, $userType));
492
493
			return;
494
		}
495
496
		if ($circleId !== '') {
497
			$localCircle = $this->circleRequest->getCircle($circleId, null, null, 0);
498
			if ($this->configService->isLocalInstance($localCircle->getInstance())) {
499
				$this->setCurrentUser($localCircle->getOwner());
500
501
				return;
502
			}
503
		}
504
505
		if (!$bypass) {
506
			throw new CircleNotFoundException(
507
				'This Circle is not managed from this instance, please use --initiator'
508
			);
509
		}
510
511
		$this->bypassCurrentUserCondition($bypass);
512
	}
513
514
515
	/**
516
	 * Works like getFederatedUser, but returns a member.
517
	 * Allow to specify a level: handle@instance,level
518
	 *
519
	 * Used for filters when searching for Circles
520
	 * TODO: Used outside of ./occ circles:manage:list ?
521
	 *
522
	 * @param string $userId
523
	 * @param int $level
524
	 *
525
	 * @return Member
526
	 * @throws CircleNotFoundException
527
	 * @throws FederatedItemException
528
	 * @throws FederatedUserException
529
	 * @throws FederatedUserNotFoundException
530
	 * @throws InvalidIdException
531
	 * @throws MemberNotFoundException
532
	 * @throws OwnerNotFoundException
533
	 * @throws RemoteInstanceException
534
	 * @throws RemoteNotFoundException
535
	 * @throws RemoteResourceNotFoundException
536
	 * @throws SingleCircleNotFoundException
537
	 * @throws UnknownRemoteException
538
	 * @throws UserTypeNotFoundException
539
	 */
540
	public function getFederatedMember(string $userId, int $level = Member::LEVEL_MEMBER): Member {
541
		$userId = trim($userId, ',');
542
		if (strpos($userId, ',') !== false) {
543
			list($userId, $level) = explode(',', $userId);
544
		}
545
546
		$federatedUser = $this->getFederatedUser($userId, Member::TYPE_USER);
547
		$member = new Member();
548
		$member->importFromIFederatedUser($federatedUser);
549
		$member->setLevel((int)$level);
550
551
		return $member;
552
	}
553
554
555
	/**
556
	 * get a valid FederatedUser, based on the federatedId (userId@instance) and its type.
557
	 * If instance is local, get the local valid FederatedUser
558
	 * If instance is not local, get the remote valid FederatedUser
559
	 *
560
	 * @param string $federatedId
561
	 * @param int $type
562
	 *
563
	 * @return FederatedUser
564
	 * @throws CircleNotFoundException
565
	 * @throws FederatedItemException
566
	 * @throws FederatedUserException
567
	 * @throws FederatedUserNotFoundException
568
	 * @throws InvalidIdException
569
	 * @throws MemberNotFoundException
570
	 * @throws OwnerNotFoundException
571
	 * @throws RemoteInstanceException
572
	 * @throws RemoteNotFoundException
573
	 * @throws RemoteResourceNotFoundException
574
	 * @throws SingleCircleNotFoundException
575
	 * @throws UnknownRemoteException
576
	 * @throws UserTypeNotFoundException
577
	 * @throws RequestBuilderException
578
	 */
579
	public function getFederatedUser(string $federatedId, int $type = Member::TYPE_SINGLE): FederatedUser {
580
		if ($type === Member::TYPE_USER) {
581
			try {
582
				return $this->getLocalFederatedUser($federatedId);
583
			} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
584
			}
585
		}
586
587
		list($singleId, $instance) = $this->extractIdAndInstance($federatedId);
588
589
		switch ($type) {
590
			case Member::TYPE_SINGLE:
591
			case Member::TYPE_CIRCLE:
592
				return $this->getFederatedUser_SingleId($singleId, $instance);
593
			case Member::TYPE_USER:
594
				return $this->getFederatedUser_User($singleId, $instance);
595
			case Member::TYPE_GROUP:
596
				return $this->getFederatedUser_Group($singleId, $instance);
597
			case Member::TYPE_MAIL:
598
				return $this->getFederatedUser_Mail($federatedId);
599
			case Member::TYPE_CONTACT:
600
				return $this->getFederatedUser_Contact($federatedId);
601
		}
602
603
		throw new UserTypeNotFoundException();
604
	}
605
606
607
	/**
608
	 * Generate a FederatedUser based on local data.
609
	 * WARNING: There is no confirmation that the returned FederatedUser exists or is valid at this point.
610
	 * Use getFederatedUser() instead if a valid and confirmed FederatedUser is needed.
611
	 *
612
	 * if $federatedId is a known SingleId, will returns data from the local database.
613
	 * if $federatedId is a local username, will returns data from the local database.
614
	 * Otherwise, the FederatedUser will not contains a SingleId.
615
	 *
616
	 * @param string $federatedId
617
	 * @param int $type
618
	 *
619
	 * @return FederatedUser
620
	 */
621
	public function generateFederatedUser(string $federatedId, int $type = 0): FederatedUser {
622
		try {
623
			return $this->getFederatedUser($federatedId, $type);
624
		} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
625
		}
626
627
		list($userId, $instance) = $this->extractIdAndInstance($federatedId);
628
		$federatedUser = new FederatedUser();
629
		$federatedUser->set($userId, $instance, $type);
630
631
		return $federatedUser;
632
	}
633
634
635
	/**
636
	 * @param FederatedUser $federatedUser
637
	 */
638
	public function deleteFederatedUser(FederatedUser $federatedUser): void {
639
		$this->circleRequest->deleteFederatedUser($federatedUser);
640
		$this->memberRequest->deleteFederatedUser($federatedUser);
641
		$this->membershipService->deleteFederatedUser($federatedUser);
642
	}
643
644
645
	/**
646
	 * @param string $singleId
647
	 * @param string $instance
648
	 *
649
	 * @return FederatedUser
650
	 * @throws CircleNotFoundException
651
	 * @throws FederatedItemException
652
	 * @throws FederatedUserException
653
	 * @throws FederatedUserNotFoundException
654
	 * @throws MemberNotFoundException
655
	 * @throws OwnerNotFoundException
656
	 * @throws RemoteInstanceException
657
	 * @throws RemoteNotFoundException
658
	 * @throws RemoteResourceNotFoundException
659
	 * @throws UnknownRemoteException
660
	 * @throws RequestBuilderException
661
	 */
662
	public function getFederatedUser_SingleId(string $singleId, string $instance): FederatedUser {
663
		if ($this->configService->isLocalInstance($instance)) {
664
			return $this->circleRequest->getFederatedUserBySingleId($singleId);
665
		} else {
666
			$federatedUser = $this->remoteService->getFederatedUserFromInstance(
667
				$singleId,
668
				$instance,
669
				Member::TYPE_SINGLE
670
			);
671
672
			$this->confirmSingleIdUniqueness($federatedUser);
673
674
			return $federatedUser;
675
		}
676
	}
677
678
679
	/**
680
	 * @param string $userId
681
	 * @param string $instance
682
	 *
683
	 * @return FederatedUser
684
	 * @throws FederatedUserException
685
	 * @throws FederatedUserNotFoundException
686
	 * @throws InvalidIdException
687
	 * @throws RemoteInstanceException
688
	 * @throws RemoteNotFoundException
689
	 * @throws RemoteResourceNotFoundException
690
	 * @throws SingleCircleNotFoundException
691
	 * @throws UnknownRemoteException
692
	 * @throws FederatedItemException
693
	 * @throws RequestBuilderException
694
	 */
695
	private function getFederatedUser_User(string $userId, string $instance): FederatedUser {
696
		if ($this->configService->isLocalInstance($instance)) {
697
			return $this->getLocalFederatedUser($userId);
698
		} else {
699
			$federatedUser = $this->remoteService->getFederatedUserFromInstance(
700
				$userId,
701
				$instance,
702
				Member::TYPE_USER
703
			);
704
705
			$this->confirmSingleIdUniqueness($federatedUser);
706
707
			return $federatedUser;
708
		}
709
	}
710
711
712
	/**
713
	 * @param string $groupName
714
	 * @param string $instance
715
	 *
716
	 * @return FederatedUser
717
	 * @throws FederatedEventException
718
	 * @throws FederatedItemException
719
	 * @throws FederatedUserException
720
	 * @throws GroupNotFoundException
721
	 * @throws InitiatorNotConfirmedException
722
	 * @throws InvalidIdException
723
	 * @throws OwnerNotFoundException
724
	 * @throws RemoteInstanceException
725
	 * @throws RemoteNotFoundException
726
	 * @throws RemoteResourceNotFoundException
727
	 * @throws SingleCircleNotFoundException
728
	 * @throws UnknownRemoteException
729
	 * @throws RequestBuilderException
730
	 */
731
	public function getFederatedUser_Group(string $groupName, string $instance): FederatedUser {
732
		if ($this->configService->isLocalInstance($instance)) {
733
			$circle = $this->getGroupCircle($groupName);
734
			$federatedGroup = new FederatedUser();
735
736
			return $federatedGroup->importFromCircle($circle);
737
		} else {
738
			// TODO: implement remote groups
739
		}
740
	}
741
742
743
	/**
744
	 * @param string $mailAddress
745
	 *
746
	 * @return FederatedUser
747
	 * @throws ContactAddressBookNotFoundException
748
	 * @throws ContactFormatException
749
	 * @throws ContactNotFoundException
750
	 * @throws FederatedUserException
751
	 * @throws InvalidIdException
752
	 * @throws RequestBuilderException
753
	 * @throws SingleCircleNotFoundException
754
	 */
755
	public function getFederatedUser_Mail(string $mailAddress): FederatedUser {
756
		$federatedUser = new FederatedUser();
757
		$federatedUser->set($mailAddress, '', Member::TYPE_MAIL);
758
		$this->fillSingleCircleId($federatedUser);
759
760
		return $federatedUser;
761
	}
762
763
764
	/**
765
	 * @param string $contactPath
766
	 *
767
	 * @return FederatedUser
768
	 * @throws ContactAddressBookNotFoundException
769
	 * @throws ContactFormatException
770
	 * @throws ContactNotFoundException
771
	 * @throws FederatedUserException
772
	 * @throws InvalidIdException
773
	 * @throws RequestBuilderException
774
	 * @throws SingleCircleNotFoundException
775
	 */
776
	public function getFederatedUser_Contact(string $contactPath): FederatedUser {
777
		$federatedUser = new FederatedUser();
778
		$federatedUser->set(
779
			$contactPath,
780
			'',
781
			Member::TYPE_CONTACT,
782
			$this->contactService->getDisplayName($contactPath)
783
		);
784
785
		$this->fillSingleCircleId($federatedUser);
786
787
		return $federatedUser;
788
	}
789
790
791
	/**
792
	 * extract userID and instance from a federatedId
793
	 *
794
	 * @param string $federatedId
795
	 *
796
	 * @return array
797
	 */
798
	public function extractIdAndInstance(string $federatedId): array {
799
		$federatedId = trim($federatedId, '@');
800
		if (strrpos($federatedId, '@') === false) {
801
			$userId = $federatedId;
802
			$instance = $this->interfaceService->getLocalInstance();
803
		} else {
804
			list($userId, $instance) = explode('@', $federatedId);
805
		}
806
807
		return [$userId, $instance];
808
	}
809
810
811
	/**
812
	 * @param FederatedUser $federatedUser
813
	 * @param bool $generate
814
	 *
815
	 * @throws ContactAddressBookNotFoundException
816
	 * @throws ContactFormatException
817
	 * @throws ContactNotFoundException
818
	 * @throws FederatedUserException
819
	 * @throws InvalidIdException
820
	 * @throws RequestBuilderException
821
	 * @throws SingleCircleNotFoundException
822
	 */
823
	private function fillSingleCircleId(FederatedUser $federatedUser, bool $generate = true): void {
824
		if ($federatedUser->getSingleId() !== '') {
825
			return;
826
		}
827
828
		$circle = $this->getSingleCircle($federatedUser, $generate);
829
		$federatedUser->setSingleId($circle->getSingleId());
830
		$federatedUser->setDisplayName($circle->getDisplayName());
831
		$federatedUser->setBasedOn($circle);
832
	}
833
834
835
	/**
836
	 * get the Single Circle from a local user
837
	 *
838
	 * @param FederatedUser $federatedUser
839
	 * @param bool $generate
840
	 *
841
	 * @return Circle
842
	 * @throws ContactAddressBookNotFoundException
843
	 * @throws ContactFormatException
844
	 * @throws ContactNotFoundException
845
	 * @throws FederatedUserException
846
	 * @throws InvalidIdException
847
	 * @throws RequestBuilderException
848
	 * @throws SingleCircleNotFoundException
849
	 */
850
	private function getSingleCircle(FederatedUser $federatedUser, bool $generate = true): Circle {
851
		if (!$this->configService->isLocalInstance($federatedUser->getInstance())) {
852
			throw new FederatedUserException('FederatedUser must be local');
853
		}
854
855
		try {
856
			return $this->circleRequest->getSingleCircle($federatedUser);
857
		} catch (SingleCircleNotFoundException $e) {
858
			if (!$generate) {
859
				throw new SingleCircleNotFoundException();
860
			}
861
862
			$circle = new Circle();
863
			$id = $this->token(ManagedModel::ID_LENGTH);
864
865
			if ($federatedUser->hasBasedOn()) {
866
				$source = $federatedUser->getBasedOn()->getSource();
867
			} else {
868
				$source = $federatedUser->getUserType();
869
			}
870
871
			$prefix = ($federatedUser->getUserType() === Member::TYPE_APP) ? 'app'
872
				: Member::$TYPE[$federatedUser->getUserType()];
873
874
			$circle->setName($prefix . ':' . $federatedUser->getUserId() . ':' . $id)
875
				   ->setDisplayName($federatedUser->getDisplayName())
876
				   ->setSingleId($id)
877
				   ->setSource($source);
878
879
			if ($federatedUser->getUserType() === Member::TYPE_APP) {
880
				$circle->setConfig(Circle::CFG_SINGLE | Circle::CFG_ROOT);
881
			} else {
882
				$circle->setConfig(Circle::CFG_SINGLE);
883
			}
884
			$this->circleRequest->save($circle);
885
886
			$owner = new Member();
887
			$owner->importFromIFederatedUser($federatedUser);
888
			$owner->setLevel(Member::LEVEL_OWNER)
889
				  ->setCircleId($id)
890
				  ->setSingleId($id)
891
				  ->setId($id)
892
				  ->setDisplayName($owner->getDisplayName())
893
				  ->setStatus('Member');
894
895
			if ($federatedUser->getUserType() !== Member::TYPE_APP) {
896
				$owner->setInvitedBy(
897
					$this->getAppInitiator(
898
						Application::APP_ID,
899
						Member::APP_CIRCLES,
900
						Application::APP_NAME
901
					)
902
				);
903
			}
904
905
			$this->memberRequest->save($owner);
906
			// TODO: should not be needed
907
			// $this->membershipService->onUpdate($id);
908
		}
909
910
		return $this->circleRequest->getSingleCircle($federatedUser);
911
	}
912
913
914
	/**
915
	 * Confirm that all field of a FederatedUser are filled.
916
	 *
917
	 * @param FederatedUser $federatedUser
918
	 *
919
	 * @throws FederatedUserException
920
	 */
921
	private function confirmFederatedUser(FederatedUser $federatedUser): void {
922
		if ($federatedUser->getUserId() === ''
923
			|| $federatedUser->getSingleId() === ''
924
			|| $federatedUser->getUserType() === 0
925
			|| $federatedUser->getInstance() === '') {
926
			$this->debug('FederatedUser is not empty', ['federatedUser' => $federatedUser]);
927
			throw new FederatedUserException('FederatedUser is not complete');
928
		}
929
	}
930
931
	/**
932
	 * Confirm that the singleId of a FederatedUser is unique and not used to any other member of the
933
	 * database.
934
	 *
935
	 * @param FederatedUser $federatedUser
936
	 *
937
	 * @throws FederatedUserException
938
	 * @throws RequestBuilderException
939
	 * @deprecated: use confirmSingleIdUniqueness()
940
	 */
941
	public function confirmLocalSingleId(IFederatedUser $federatedUser): void {
942
		$members = $this->memberRequest->getMembersBySingleId($federatedUser->getSingleId());
943
944
		foreach ($members as $member) {
945
			if (!$federatedUser->compareWith($member)) {
946
				$this->debug(
947
					'uniqueness of SingleId could not be confirmed',
948
					['federatedUser' => $federatedUser, 'localMember' => $member]
949
				);
950
				throw new FederatedUserException('uniqueness of SingleId could not be confirmed');
951
			}
952
		}
953
	}
954
955
956
	/**
957
	 * // TODO: implement this check in a maintenance background job
958
	 *
959
	 * @param IFederatedUser $federatedUser
960
	 *
961
	 * @throws FederatedUserException
962
	 * @throws RequestBuilderException
963
	 */
964
	public function confirmSingleIdUniqueness(IFederatedUser $federatedUser): void {
965
		// TODO: use aliases!
966
		// TODO: check also with Circles singleId
967
968
969
		$remote = null;
970
		if (!$this->configService->isLocalInstance($federatedUser->getInstance())) {
971
			$remote = $this->remoteStreamService->getCachedRemoteInstance($federatedUser->getInstance());
972
		}
973
974
		$knownMembers = $this->memberRequest->getAlternateSingleId($federatedUser);
975
		foreach ($knownMembers as $knownMember) {
976
			if ($this->configService->isLocalInstance($federatedUser->getInstance())) {
977
				if ($this->configService->isLocalInstance($knownMember->getInstance())) {
978
					return;
979
				} else {
980
					throw new FederatedUserException('uniqueness of SingleId could not be confirmed (001)');
981
				}
982
			}
983
984
			if (!$knownMember->hasRemoteInstance()) {
985
				throw new FederatedUserException('uniqueness of SingleId could not be confirmed (002)');
986
			}
987
988
			$knownRemote = $knownMember->getRemoteInstance();
989
			if ($this->interfaceService->isInterfaceInternal($knownRemote->getInterface())
990
				&& !in_array($federatedUser->getInstance(), $knownRemote->getAliases())) {
991
				throw new FederatedUserException('uniqueness of SingleId could not be confirmed (003)');
992
			}
993
994
			if (is_null($remote)) {
995
				throw new FederatedUserException('uniqueness of SingleId could not be confirmed (004)');
996
			}
997
998
			if ($this->interfaceService->isInterfaceInternal($remote->getInterface())
999
				&& !in_array($knownMember->getInstance(), $remote->getAliases())) {
1000
				throw new FederatedUserException('uniqueness of SingleId could not be confirmed (005)');
1001
			}
1002
		}
1003
	}
1004
1005
1006
	/**
1007
	 * @param string $groupId
1008
	 *
1009
	 * @return Circle
1010
	 * @throws GroupNotFoundException
1011
	 * @throws FederatedEventException
1012
	 * @throws FederatedItemException
1013
	 * @throws FederatedUserException
1014
	 * @throws InitiatorNotConfirmedException
1015
	 * @throws InvalidIdException
1016
	 * @throws OwnerNotFoundException
1017
	 * @throws RemoteInstanceException
1018
	 * @throws RemoteNotFoundException
1019
	 * @throws RemoteResourceNotFoundException
1020
	 * @throws SingleCircleNotFoundException
1021
	 * @throws UnknownRemoteException
1022
	 * @throws RequestBuilderException
1023
	 */
1024
	public function getGroupCircle(string $groupId): Circle {
1025
		$group = $this->groupManager->get($groupId);
1026
		if ($group === null) {
1027
			throw new GroupNotFoundException('group not found');
1028
		}
1029
1030
		$this->setLocalCurrentApp(Application::APP_ID, Member::APP_CIRCLES);
1031
		$owner = $this->getCurrentApp();
1032
1033
		$circle = new Circle();
1034
		$circle->setName('group:' . $groupId)
1035
			   ->setConfig(Circle::CFG_SYSTEM | Circle::CFG_NO_OWNER | Circle::CFG_HIDDEN)
1036
			   ->setSingleId($this->token(ManagedModel::ID_LENGTH))
1037
			   ->setSource(Member::TYPE_GROUP);
1038
1039
		$member = new Member();
1040
		$member->importFromIFederatedUser($owner);
0 ignored issues
show
Bug introduced by
It seems like $owner defined by $this->getCurrentApp() on line 1031 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...
1041
		$member->setId($this->token(ManagedModel::ID_LENGTH))
1042
			   ->setCircleId($circle->getSingleId())
1043
			   ->setLevel(Member::LEVEL_OWNER)
1044
			   ->setStatus(Member::STATUS_MEMBER);
1045
		$circle->setOwner($member)
1046
			   ->setInitiator($member);
1047
1048
		try {
1049
			return $this->circleRequest->searchCircle($circle, $owner);
1050
		} catch (CircleNotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1051
		}
1052
1053
		$circle->setDisplayName($groupId);
1054
1055
		$event = new FederatedEvent(CircleCreate::class);
1056
		$event->setCircle($circle);
1057
		$this->federatedEventService->newEvent($event);
1058
1059
		return $circle;
1060
	}
1061
1062
}
1063
1064