Completed
Pull Request — master (#551)
by Maxence
02:35
created

FederatedUserService::generateFederatedUser()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
cc 3
nc 3
nop 2
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\Exceptions\InvalidItemException;
36
use daita\MySmallPhpTools\Exceptions\RequestNetworkException;
37
use daita\MySmallPhpTools\Exceptions\SignatoryException;
38
use daita\MySmallPhpTools\Traits\Nextcloud\nc21\TNC21Logger;
39
use daita\MySmallPhpTools\Traits\TArrayTools;
40
use daita\MySmallPhpTools\Traits\TStringTools;
41
use OCA\Circles\Db\CircleRequest;
42
use OCA\Circles\Db\MemberRequest;
43
use OCA\Circles\Db\MembershipRequest;
44
use OCA\Circles\Exceptions\CircleNotFoundException;
45
use OCA\Circles\Exceptions\FederatedUserException;
46
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
47
use OCA\Circles\Exceptions\InitiatorNotFoundException;
48
use OCA\Circles\Exceptions\InvalidIdException;
49
use OCA\Circles\Exceptions\MemberNotFoundException;
50
use OCA\Circles\Exceptions\OwnerNotFoundException;
51
use OCA\Circles\Exceptions\RemoteInstanceException;
52
use OCA\Circles\Exceptions\RemoteNotFoundException;
53
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
54
use OCA\Circles\Exceptions\UnknownRemoteException;
55
use OCA\Circles\Exceptions\UserTypeNotFoundException;
56
use OCA\Circles\IFederatedUser;
57
use OCA\Circles\Model\Circle;
58
use OCA\Circles\Model\Federated\RemoteInstance;
59
use OCA\Circles\Model\FederatedUser;
60
use OCA\Circles\Model\ManagedModel;
61
use OCA\Circles\Model\Member;
62
use OCP\IUserManager;
63
64
65
/**
66
 * Class FederatedUserService
67
 *
68
 * @package OCA\Circles\Service
69
 */
70
class FederatedUserService {
71
72
73
	use TArrayTools;
74
	use TStringTools;
75
	use TNC21Logger;
76
77
78
	/** @var IUserManager */
79
	private $userManager;
80
81
	/** @var MembershipRequest */
82
	private $membershipRequest;
83
84
	/** @var CircleRequest */
85
	private $circleRequest;
86
87
	/** @var MemberRequest */
88
	private $memberRequest;
89
90
	/** @var RemoteService */
91
	private $remoteService;
92
93
	/** @var ConfigService */
94
	private $configService;
95
96
97
	/** @var FederatedUser */
98
	private $currentUser = null;
99
100
	/** @var RemoteInstance */
101
	private $remoteInstance = null;
102
103
	/** @var bool */
104
	private $bypass = false;
105
106
107
	/**
108
	 * FederatedUserService constructor.
109
	 *
110
	 * @param IUserManager $userManager
111
	 * @param MembershipRequest $membershipRequest
112
	 * @param CircleRequest $circleRequest
113
	 * @param MemberRequest $memberRequest
114
	 * @param RemoteService $remoteService
115
	 * @param ConfigService $configService
116
	 */
117
	public function __construct(
118
		IUserManager $userManager, MembershipRequest $membershipRequest, CircleRequest $circleRequest,
119
		MemberRequest $memberRequest, RemoteService $remoteService, ConfigService $configService
120
	) {
121
		$this->userManager = $userManager;
122
		$this->membershipRequest = $membershipRequest;
123
		$this->circleRequest = $circleRequest;
124
		$this->memberRequest = $memberRequest;
125
		$this->remoteService = $remoteService;
126
		$this->configService = $configService;
127
	}
128
129
130
//	/**
131
//	 * @param string $userId
132
//	 *
133
//	 * @throws CircleNotFoundException
134
//	 * @throws NoUserException
135
//	 */
136
//	public function setLocalInitiator(string $userId): void {
137
//		$this->currentUser = $this->createLocalFederatedUser($userId);
138
//	}
139
140
	/**
141
	 * set a CurrentUser, based on a IFederatedUser.
142
	 * CurrentUser is mainly used to manage rights when requesting the database.
143
	 *
144
	 * @param IFederatedUser $federatedUser
145
	 *
146
	 * @throws FederatedUserException
147
	 */
148
	public function setCurrentUser(IFederatedUser $federatedUser): void {
149
		if (!($federatedUser instanceof FederatedUser)) {
150
			$tmp = new FederatedUser();
151
			$tmp->importFromIFederatedUser($federatedUser);
152
			$federatedUser = $tmp;
153
		}
154
155
		$this->confirmFederatedUser($federatedUser);
156
157
		$this->currentUser = $federatedUser;
158
	}
159
160
	/**
161
	 * @return FederatedUser|null
162
	 */
163
	public function getCurrentUser(): ?FederatedUser {
164
		return $this->currentUser;
165
	}
166
167
	/**
168
	 * @return bool
169
	 */
170
	public function hasCurrentUser(): bool {
171
		return !is_null($this->currentUser);
172
	}
173
174
	/**
175
	 * @throws InitiatorNotFoundException
176
	 */
177
	public function mustHaveCurrentUser(): void {
178
		if ($this->bypass) {
179
			return;
180
		}
181
		if (!$this->hasCurrentUser() && !$this->hasRemoteInstance()) {
182
			throw new InitiatorNotFoundException();
183
		}
184
	}
185
186
	/**
187
	 * @param bool $bypass
188
	 */
189
	public function bypassCurrentUserCondition(bool $bypass): void {
190
		$this->bypass = $bypass;
191
	}
192
193
194
	/**
195
	 * set a RemoteInstance, mostly from a remote request (RemoteController)
196
	 * Used to limit rights in some request in the local database.
197
	 *
198
	 * @param RemoteInstance $remoteInstance
199
	 */
200
	public function setRemoteInstance(RemoteInstance $remoteInstance): void {
201
		$this->remoteInstance = $remoteInstance;
202
	}
203
204
	/**
205
	 * @return RemoteInstance|null
206
	 */
207
	public function getRemoteInstance(): ?RemoteInstance {
208
		return $this->remoteInstance;
209
	}
210
211
	/**
212
	 * @return bool
213
	 */
214
	public function hasRemoteInstance(): bool {
215
		return !is_null($this->remoteInstance);
216
	}
217
218
219
	/**
220
	 * Get the full FederatedUser for a local user.
221
	 * Will generate the SingleId if none exist
222
	 *
223
	 * @param string $userId
224
	 *
225
	 * @return FederatedUser
226
	 * @throws CircleNotFoundException
227
	 * @throws FederatedUserNotFoundException
228
	 * @throws InvalidIdException
229
	 */
230
	public function getLocalFederatedUser(string $userId): FederatedUser {
231
		$user = $this->userManager->get($userId);
232
		if ($user === null) {
233
			throw new FederatedUserNotFoundException('user ' . $userId . ' not found');
234
		}
235
236
		$federatedUser = new FederatedUser();
237
		$federatedUser->set($user->getUID());
238
		$this->fillSingleCircleId($federatedUser);
239
240
		return $federatedUser;
241
	}
242
243
244
	/**
245
	 * some ./occ commands allows to add an Initiator, or force the PoV from the local circles' owner
246
	 *
247
	 * TODO: manage non-user type ?
248
	 *
249
	 * @param string $userId
250
	 * @param string $circleId
251
	 * @param bool $bypass
252
	 *
253
	 * @throws CircleNotFoundException
254
	 * @throws FederatedUserException
255
	 * @throws FederatedUserNotFoundException
256
	 * @throws InvalidIdException
257
	 * @throws InvalidItemException
258
	 * @throws OwnerNotFoundException
259
	 * @throws RemoteInstanceException
260
	 * @throws RemoteNotFoundException
261
	 * @throws RemoteResourceNotFoundException
262
	 * @throws RequestNetworkException
263
	 * @throws SignatoryException
264
	 * @throws UnknownRemoteException
265
	 * @throws UserTypeNotFoundException
266
	 */
267
	public function commandLineInitiator(string $userId, string $circleId = '', bool $bypass = false): void {
268
		if ($userId !== '') {
269
			$this->setCurrentUser($this->getFederatedUser($userId));
270
271
			return;
272
		}
273
274
		if ($circleId !== '') {
275
			$localCircle = $this->circleRequest->getCircle($circleId);
276
			if ($this->configService->isLocalInstance($localCircle->getInstance())) {
277
				// TODO: manage NO_OWNER circles
278
				$this->setCurrentUser($localCircle->getOwner());
279
280
				return;
281
			}
282
		}
283
284
		if (!$bypass) {
285
			throw new CircleNotFoundException(
286
				'This Circle is not managed from this instance, please use --initiator'
287
			);
288
		}
289
290
		$this->bypassCurrentUserCondition($bypass);
291
	}
292
293
294
	/**
295
	 * Works like getFederatedUser, but returns a member.
296
	 * Allow to specify a level: handle@instance,level
297
	 *
298
	 * Used for filters when searching for Circles
299
	 * TODO: Used outside of ./occ circles:manage:list ?
300
	 *
301
	 * @param string $userId
302
	 * @param int $level
303
	 *
304
	 * @return Member
305
	 * @throws CircleNotFoundException
306
	 * @throws FederatedUserException
307
	 * @throws FederatedUserNotFoundException
308
	 * @throws InvalidIdException
309
	 * @throws InvalidItemException
310
	 * @throws OwnerNotFoundException
311
	 * @throws RemoteInstanceException
312
	 * @throws RemoteNotFoundException
313
	 * @throws RemoteResourceNotFoundException
314
	 * @throws RequestNetworkException
315
	 * @throws SignatoryException
316
	 * @throws UnknownRemoteException
317
	 * @throws UserTypeNotFoundException
318
	 */
319
	public function getFederatedMember(string $userId, int $level = Member::LEVEL_MEMBER): Member {
320
		$userId = trim($userId, ',');
321
		if (strpos($userId, ',') !== false) {
322
			list($userId, $level) = explode(',', $userId);
323
		}
324
325
		$federatedUser = $this->getFederatedUser($userId);
326
		$member = new Member();
327
		$member->importFromIFederatedUser($federatedUser);
328
		$member->setLevel((int)$level);
329
330
		return $member;
331
	}
332
333
334
	/**
335
	 * Generate a FederatedUser based on local data.
336
	 * WARNING: There is no confirmation that the returned FederatedUser exists or is valid at this point.
337
	 * Use getFederatedUser() instead if a valid and confirmed FederatedUser is needed.
338
	 *
339
	 * if $federatedId is a known SingleId, will returns data from the local database.
340
	 * if $federatedId is a local username, will returns data from the local database.
341
	 * Otherwise, the FederatedUser will not contains a SingleId.
342
	 *
343
	 * @param string $federatedId
344
	 * @param int $type
345
	 *
346
	 * @return FederatedUser
347
	 */
348
	public function generateFederatedUser(string $federatedId, int $type = Member::TYPE_USER): FederatedUser {
349
		try {
350
			return $this->memberRequest->getFederatedUserBySingleId($federatedId);
351
		} catch (MemberNotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
352
		}
353
354
		try {
355
			return $this->getLocalFederatedUser($federatedId);
356
		} catch (CircleNotFoundException | FederatedUserNotFoundException | InvalidIdException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
357
		}
358
359
		// TODO: search for other possible entries, like mail address
360
		list($userId, $instance) = $this->extractIdAndInstance($federatedId);
361
		$federatedUser = new FederatedUser();
362
		$federatedUser->set($userId, $instance, $type);
363
364
		return $federatedUser;
365
	}
366
367
368
	/**
369
	 * get a valid FederatedUser, based on the federatedId (userId@instance) its the type.
370
	 * If instance is local, get the local valid FederatedUser
371
	 * If instance is not local, get the remote valid FederatedUser
372
	 *
373
	 * @param string $federatedId
374
	 * @param int $userType
375
	 *
376
	 * @return FederatedUser
377
	 * @throws CircleNotFoundException
378
	 * @throws FederatedUserException
379
	 * @throws FederatedUserNotFoundException
380
	 * @throws InvalidIdException
381
	 * @throws InvalidItemException
382
	 * @throws RemoteInstanceException
383
	 * @throws RemoteNotFoundException
384
	 * @throws RemoteResourceNotFoundException
385
	 * @throws RequestNetworkException
386
	 * @throws SignatoryException
387
	 * @throws UnknownRemoteException
388
	 * @throws UserTypeNotFoundException
389
	 * @throws OwnerNotFoundException
390
	 */
391
	public function getFederatedUser(string $federatedId, int $userType = Member::TYPE_USER): FederatedUser {
392
		switch ($userType) {
393
			case Member::TYPE_USER:
394
				return $this->getFederatedUser_User($federatedId);
395
			case Member::TYPE_CIRCLE:
396
				return $this->getFederatedUser_Circle($federatedId);
397
		}
398
399
		throw new UserTypeNotFoundException();
400
	}
401
402
403
	/**
404
	 * @param string $federatedId
405
	 *
406
	 * @return FederatedUser
407
	 * @throws CircleNotFoundException
408
	 * @throws FederatedUserNotFoundException
409
	 * @throws InvalidIdException
410
	 * @throws RemoteInstanceException
411
	 * @throws RemoteNotFoundException
412
	 * @throws RemoteResourceNotFoundException
413
	 * @throws UnknownRemoteException
414
	 * @throws InvalidItemException
415
	 * @throws RequestNetworkException
416
	 * @throws SignatoryException
417
	 * @throws FederatedUserException
418
	 */
419
	private function getFederatedUser_User(string $federatedId): FederatedUser {
420
		list($userId, $instance) = $this->extractIdAndInstance($federatedId);
421
422
		if ($this->configService->isLocalInstance($instance)) {
423
			return $this->getLocalFederatedUser($userId);
424
		} else {
425
			$federatedUser =
426
				$this->remoteService->getFederatedUserFromInstance($userId, $instance, Member::TYPE_USER);
427
			$this->confirmLocalSingleId($federatedUser);
428
429
			return $federatedUser;
430
		}
431
	}
432
433
434
	/**
435
	 * // TODO: do we need to have this working on remote instance ?
436
	 *
437
	 * @param string $federatedId
438
	 *
439
	 * @return FederatedUser
440
	 * @throws CircleNotFoundException
441
	 * @throws OwnerNotFoundException
442
	 */
443
	private function getFederatedUser_Circle(string $federatedId): FederatedUser {
444
		list($circleId, $instance) = $this->extractIdAndInstance($federatedId);
0 ignored issues
show
Unused Code introduced by
The assignment to $instance is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
445
446
		// TODO: check remote instance for existing circle
447
//		if ($this->configService->isLocalInstance($instance)) {
448
		$circle = $this->circleRequest->getCircle($circleId);
449
		$federatedUser = new FederatedUser();
450
		$federatedUser->set($circleId, $circle->getInstance(), Member::TYPE_CIRCLE);
451
		$federatedUser->setSingleId($circleId);
452
//		} else {
453
//		}
454
455
		return $federatedUser;
456
	}
457
458
459
	/**
460
	 * extract userID and instance from a federatedId
461
	 *
462
	 * @param string $federatedId
463
	 *
464
	 * @return array
465
	 */
466
	private function extractIdAndInstance(string $federatedId): array {
467
		$federatedId = trim($federatedId, '@');
468
		if (strpos($federatedId, '@') === false) {
469
			$userId = $federatedId;
470
			$instance = $this->configService->getLocalInstance();
471
		} else {
472
			list($userId, $instance) = explode('@', $federatedId);
473
		}
474
475
		return [$userId, $instance];
476
	}
477
478
479
	/**
480
	 * @param FederatedUser $federatedUser
481
	 *
482
	 * @throws CircleNotFoundException
483
	 * @throws InvalidIdException
484
	 */
485
	private function fillSingleCircleId(FederatedUser $federatedUser): void {
486
		if ($federatedUser->getSingleId() !== '') {
487
			return;
488
		}
489
490
		$circle = $this->getSingleCircle($federatedUser);
491
		$federatedUser->setSingleId($circle->getId());
492
	}
493
494
495
	/**
496
	 * get the Single Circle from a local user
497
	 *
498
	 * @param FederatedUser $federatedUser
499
	 *
500
	 * @return Circle
501
	 * @throws CircleNotFoundException
502
	 * @throws InvalidIdException
503
	 * @throws FederatedUserException
504
	 */
505
	private function getSingleCircle(FederatedUser $federatedUser): Circle {
506
		if (!$this->configService->isLocalInstance($federatedUser->getInstance())) {
507
			throw new FederatedUserException('FederatedUser must be local');
508
		}
509
510
		try {
511
			return $this->circleRequest->getInitiatorCircle($federatedUser);
512
		} catch (CircleNotFoundException $e) {
513
			$circle = new Circle();
514
			$id = $this->token(ManagedModel::ID_LENGTH);
515
516
			$circle->setName('single:' . $federatedUser->getUserId() . ':' . $id)
517
				   ->setId($id)
518
				   ->setConfig(Circle::CFG_SINGLE);
519
			$this->circleRequest->save($circle);
520
521
			$owner = new Member();
522
			$owner->importFromIFederatedUser($federatedUser);
523
			$owner->setLevel(Member::LEVEL_OWNER)
524
				  ->setCircleId($id)
525
				  ->setId($id)
526
				  ->setCachedName($owner->getUserId())
527
				  ->setStatus('Member');
528
			$this->memberRequest->save($owner);
529
		}
530
531
		return $this->circleRequest->getInitiatorCircle($federatedUser);
532
	}
533
534
535
	/**
536
	 * Confirm that all field of a FederatedUser are filled.
537
	 *
538
	 * @param FederatedUser $federatedUser
539
	 *
540
	 * @throws FederatedUserException
541
	 */
542
	private function confirmFederatedUser(FederatedUser $federatedUser): void {
543
		if ($federatedUser->getUserId() === ''
544
			|| $federatedUser->getSingleId() === ''
545
			|| $federatedUser->getUserType() === ''
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $federatedUser->getUserType() (integer) and '' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
546
			|| $federatedUser->getInstance() === '') {
547
			$this->debug('FederatedUser is not empty', ['federatedUser' => $federatedUser]);
548
			throw new FederatedUserException('FederatedUser is not complete');
549
		}
550
	}
551
552
	/**
553
	 * Confirm that the singleId of a FederatedUser is unique and not used to any other member of the
554
	 * database.
555
	 *
556
	 * @param FederatedUser $federatedUser
557
	 *
558
	 * @throws FederatedUserException
559
	 */
560
	public function confirmLocalSingleId(FederatedUser $federatedUser): void {
561
		$members = $this->memberRequest->getMembersBySingleId($federatedUser->getSingleId());
562
563
		foreach ($members as $member) {
564
			if (!$federatedUser->compareWith($member)) {
565
				$this->debug(
566
					'uniqueness of SingleId could not be confirmed',
567
					['federatedUser' => $federatedUser, 'localMember' => $member]
568
				);
569
				throw new FederatedUserException('uniqueness of SingleId could not be confirmed');
570
			}
571
		}
572
	}
573
574
575
//	/**
576
//	 * @param FederatedUser $federatedUser
577
//	 *
578
//	 * @return Membership[]
579
//	 */
580
//	public function generateMemberships(FederatedUser $federatedUser): array {
581
//		$circles = $this->circleRequest->getCircles(null, $federatedUser);
582
//		$memberships = [];
583
//		foreach ($circles as $circle) {
584
//			$initiator = $circle->getInitiator();
585
//			if (!$initiator->isMember()) {
586
//				continue;
587
//			}
588
//
589
//			$memberships[] = new Membership(
590
//				$initiator->getId(), $circle->getId(), $federatedUser->getSingleId(), $initiator->getLevel()
591
//			);
592
//
593
////			$newUser = new CurrentUser($circle->getId(), Member::TYPE_CIRCLE, '');
594
////			$circles = $this->circleRequest->getCircles(null, $currentUser);
595
//		}
596
//
597
//		return $memberships;
598
//	}
599
//
600
//
601
//	/**
602
//	 * @param FederatedUser|null $federatedUser
603
//	 */
604
//	public function updateMemberships(?FederatedUser $federatedUser = null) {
605
//		if (is_null($federatedUser)) {
606
//			$federatedUser = $this->getCurrentUser();
607
//		} else {
608
//			$federatedUser->setMemberships($this->membershipRequest->getMemberships($federatedUser));
609
//		}
610
//
611
//		if (is_null($federatedUser)) {
612
//			return;
613
//		}
614
//
615
//		$last = $this->generateMemberships($federatedUser);
616
//
617
//		echo 'known: ' . json_encode($federatedUser->getMemberships()) . "\n";
618
//		echo 'last: ' . json_encode($last) . "\n";
619
//
620
////
621
////		$circles = $this->circleRequest->getCircles(null, $viewer);
622
////		foreach ($circles as $circle) {
623
////			$viewer = $circle->getViewer();
624
////			if (!$viewer->isMember()) {
625
////				continue;
626
////			}
627
////
628
////			echo 'new member: ' . json_encode($viewer) . "\n";
629
//////			$this->federatedUserService->updateMembership($circle);
630
////		}
631
//
632
//
633
//	}
634
635
636
}
637
638