Completed
Pull Request — master (#609)
by Maxence
02:38
created

FederatedUserService::setInitiatedByOcc()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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