Completed
Push — master ( 1f8246...effc67 )
by Maxence
02:37
created

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