Completed
Pull Request — master (#614)
by Maxence
02:18
created

FederatedUserService   F

Complexity

Total Complexity 84

Size/Duplication

Total Lines 925
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 23

Importance

Changes 0
Metric Value
wmc 84
lcom 1
cbo 23
dl 0
loc 925
rs 1.675
c 0
b 0
f 0

38 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 22 2
A initCurrentUser() 0 8 2
A setLocalCurrentUser() 0 7 2
A setLocalCurrentUserId() 0 3 1
A setLocalCurrentApp() 0 3 1
A setCurrentUser() 0 11 2
A unsetCurrentUser() 0 3 1
A getCurrentUser() 0 3 1
A hasCurrentUser() 0 3 1
A mustHaveCurrentUser() 0 8 4
A bypassCurrentUserCondition() 0 3 1
A setInitiatedByOcc() 0 3 1
A isInitiatedByOcc() 0 3 1
A setMemberPatron() 0 7 2
A getCurrentApp() 0 3 1
A hasCurrentApp() 0 3 1
A setRemoteInstance() 0 3 1
A getRemoteInstance() 0 3 1
A hasRemoteInstance() 0 3 1
A getLocalFederatedUser() 0 17 3
A getAppInitiator() 0 17 2
A commandLineInitiator() 0 29 5
A getFederatedMember() 0 13 2
B getFederatedUser() 0 25 9
A generateFederatedUser() 0 12 2
A deleteFederatedUser() 0 5 1
A getFederatedUser_SingleId() 0 15 2
A getFederatedUser_User() 0 11 2
A getFederatedUser_Group() 0 10 2
A getFederatedUser_Mail() 0 7 1
A getFederatedUser_Contact() 0 13 1
A extractIdAndInstance() 0 11 2
A fillSingleCircleId() 0 10 2
B getSingleCircle() 0 62 8
A confirmFederatedUser() 0 9 5
A confirmLocalSingleId() 0 13 3
A confirmSingleIdUniqueness() 0 7 2
A getGroupCircle() 0 37 3

How to fix   Complexity   

Complex Class

Complex classes like FederatedUserService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FederatedUserService, and based on these observations, apply Extract Interface, too.

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