Completed
Pull Request — master (#625)
by Maxence
02:28
created

FederatedUserService::setLocalCurrentUser()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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