Completed
Pull Request — master (#362)
by Maxence
02:14
created

MembersService::verifyIdentLocalMember()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
cc 4
nc 4
nop 3
1
<?php
2
/**
3
 * Circles - bring cloud-users closer
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Maxence Lange <[email protected]>
9
 * @copyright 2017
10
 * @license GNU AGPL version 3 or any later version
11
 *
12
 * This program is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License as
14
 * published by the Free Software Foundation, either version 3 of the
15
 * License, or (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
 *
25
 */
26
27
namespace OCA\Circles\Service;
28
29
30
use Exception;
31
use OC;
32
use OC\User\NoUserException;
33
use OCA\Circles\Circles\FileSharingBroadcaster;
34
use OCA\Circles\Db\CirclesRequest;
35
use OCA\Circles\Db\MembersRequest;
36
use OCA\Circles\Db\SharesRequest;
37
use OCA\Circles\Db\TokensRequest;
38
use OCA\Circles\Exceptions\CircleDoesNotExistException;
39
use OCA\Circles\Exceptions\CircleTypeNotValidException;
40
use OCA\Circles\Exceptions\ConfigNoCircleAvailableException;
41
use OCA\Circles\Exceptions\EmailAccountInvalidFormatException;
42
use OCA\Circles\Exceptions\GroupDoesNotExistException;
43
use OCA\Circles\Exceptions\MemberAlreadyExistsException;
44
use OCA\Circles\Exceptions\MemberCantJoinCircleException;
45
use OCA\Circles\Exceptions\MemberDoesNotExistException;
46
use OCA\Circles\Exceptions\MemberIsNotModeratorException;
47
use OCA\Circles\Model\Circle;
48
use OCA\Circles\Model\GlobalScale\GSEvent;
49
use OCA\Circles\Model\Member;
50
use OCP\IL10N;
51
use OCP\IUserManager;
52
53
54
/**
55
 * Class MembersService
56
 *
57
 * @package OCA\Circles\Service
58
 */
59
class MembersService {
60
61
	/** @var string */
62
	private $userId;
63
64
	/** @var IL10N */
65
	private $l10n;
66
67
	/** @var IUserManager */
68
	private $userManager;
69
70
	/** @var ConfigService */
71
	private $configService;
72
73
	/** @var CirclesRequest */
74
	private $circlesRequest;
75
76
	/** @var MembersRequest */
77
	private $membersRequest;
78
79
	/** @var SharesRequest */
80
	private $sharesRequest;
81
82
	/** @var TokensRequest */
83
	private $tokensRequest;
84
85
	/** @var CirclesService */
86
	private $circlesService;
87
88
	/** @var EventsService */
89
	private $eventsService;
90
91
	/** @var GSUpstreamService */
92
	private $gsUpstreamService;
93
94
	/** @var FileSharingBroadcaster */
95
	private $fileSharingBroadcaster;
96
97
	/** @var MiscService */
98
	private $miscService;
99
100
	/**
101
	 * MembersService constructor.
102
	 *
103
	 * @param string $userId
104
	 * @param IL10N $l10n
105
	 * @param IUserManager $userManager
106
	 * @param ConfigService $configService
107
	 * @param CirclesRequest $circlesRequest
108
	 * @param MembersRequest $membersRequest
109
	 * @param SharesRequest $sharesRequest
110
	 * @param TokensRequest $tokensRequest
111
	 * @param CirclesService $circlesService
112
	 * @param EventsService $eventsService
113
	 * @param FileSharingBroadcaster $fileSharingBroadcaster
114
	 * @param MiscService $miscService
115
	 */
116 View Code Duplication
	public function __construct(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
117
		$userId, IL10N $l10n, IUserManager $userManager, ConfigService $configService,
118
		CirclesRequest $circlesRequest, MembersRequest $membersRequest, SharesRequest $sharesRequest,
119
		TokensRequest $tokensRequest, CirclesService $circlesService, EventsService $eventsService,
120
		GSUpstreamService $gsUpstreamService, FileSharingBroadcaster $fileSharingBroadcaster,
121
		MiscService $miscService
122
	) {
123
		$this->userId = $userId;
124
		$this->l10n = $l10n;
125
		$this->userManager = $userManager;
126
		$this->configService = $configService;
127
		$this->circlesRequest = $circlesRequest;
128
		$this->membersRequest = $membersRequest;
129
		$this->sharesRequest = $sharesRequest;
130
		$this->tokensRequest = $tokensRequest;
131
		$this->circlesService = $circlesService;
132
		$this->eventsService = $eventsService;
133
		$this->gsUpstreamService = $gsUpstreamService;
134
		$this->fileSharingBroadcaster = $fileSharingBroadcaster;
135
		$this->miscService = $miscService;
136
	}
137
138
139
	/**
140
	 * addMember();
141
	 *
142
	 * add a new member to a circle.
143
	 *
144
	 * @param string $circleUniqueId
145
	 * @param $ident
146
	 * @param int $type
147
	 * @param string $instance
148
	 *
149
	 * @param bool $force
150
	 *
151
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use Member[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
152
	 * @throws Exception
153
	 */
154
	public function addMember($circleUniqueId, $ident, $type, string $instance, bool $force = false) {
155 View Code Duplication
		if ($force === true) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
156
			$circle = $this->circlesRequest->forceGetCircle($circleUniqueId);
157
		} else {
158
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
159
			$circle->getHigherViewer()
160
				   ->hasToBeModerator();
161
		}
162
163
		if (!$this->addMassiveMembers($circle, $ident, $type)) {
164
			$this->addSingleMember($circle, $ident, $type, $instance, $force);
165
		}
166
167
		return $this->membersRequest->getMembers($circle->getUniqueId(), $circle->getHigherViewer(), $force);
168
	}
169
170
171
	/**
172
	 * add a single member to a circle.
173
	 *
174
	 * @param Circle $circle
175
	 * @param string $ident
176
	 * @param int $type
177
	 *
178
	 * @param string $instance
179
	 * @param bool $force
180
	 *
181
	 * @throws EmailAccountInvalidFormatException
182
	 * @throws NoUserException
183
	 */
184
	private function addSingleMember(Circle $circle, $ident, $type, $instance = '', bool $force = false) {
185
186
		$this->verifyIdentBasedOnItsType($ident, $type, $instance);
187
188
		$member = $this->membersRequest->getFreshNewMember($circle->getUniqueId(), $ident, $type, $instance);
189
190
		$event = new GSEvent(GSEvent::MEMBER_ADD, false, $force);
191
		$event->setSeverity(GSEvent::SEVERITY_HIGH);
192
193
		$event->setCircle($circle);
194
		$event->setMember($member);
195
		$this->gsUpstreamService->newEvent($event);
196
	}
197
198
199
	/**
200
	 * add a bunch of users to a circle based on the type of the 'bunch'
201
	 *
202
	 * @param Circle $circle
203
	 * @param string $ident
204
	 * @param int $type
205
	 *
206
	 * @return bool
207
	 * @throws Exception
208
	 */
209
	private function addMassiveMembers(Circle $circle, $ident, $type) {
210
		if ($type === Member::TYPE_GROUP) {
211
			return $this->addGroupMembers($circle, $ident);
212
		}
213
214
		if ($type === Member::TYPE_USER) {
215
			return $this->addMassiveMails($circle, $ident);
216
		}
217
218
		return false;
219
	}
220
221
222
	/**
223
	 * add a new member based on its type.
224
	 *
225
	 * @param Circle $circle
226
	 * @param Member $member
227
	 *
228
	 * @throws CircleTypeNotValidException
229
	 * @throws MemberCantJoinCircleException
230
	 */
231
	public function addMemberBasedOnItsType(Circle $circle, Member &$member) {
232
		$this->addLocalMember($circle, $member);
233
		$this->addEmailAddress($member);
234
		$this->addContact($member);
235
	}
236
237
238
	/**
239
	 * @param Circle $circle
240
	 * @param Member $member
241
	 *
242
	 * @throws CircleTypeNotValidException
243
	 * @throws MemberCantJoinCircleException
244
	 */
245
	private function addLocalMember(Circle $circle, Member $member) {
246
247
		if ($member->getType() !== Member::TYPE_USER) {
248
			return;
249
		}
250
251
		$member->inviteToCircle($circle->getType());
252
253
		if ($circle->getType() === Circle::CIRCLES_CLOSED && $this->configService->isInvitationSkipped()) {
254
			$member->joinCircle($circle->getType());
255
		}
256
	}
257
258
259
	/**
260
	 * add mail address as contact.
261
	 *
262
	 * @param Member $member
263
	 */
264
	private function addEmailAddress(Member $member) {
265
266
		if ($member->getType() !== Member::TYPE_MAIL) {
267
			return;
268
		}
269
270
		$member->addMemberToCircle();
271
	}
272
273
274
	/**
275
	 * // TODO - check this on GS setup
276
	 * Add contact as member.
277
	 *
278
	 * @param Member $member
279
	 */
280
	private function addContact(Member $member) {
281
282
		if ($member->getType() !== Member::TYPE_CONTACT) {
283
			return;
284
		}
285
286
		$member->addMemberToCircle();
287
	}
288
289
290
	/**
291
	 * // TODO - check this on GS setup
292
	 * Verify the availability of an ident, based on its type.
293
	 *
294
	 * @param string $ident
295
	 * @param int $type
296
	 * @param string $instance
297
	 *
298
	 * @throws EmailAccountInvalidFormatException
299
	 * @throws NoUserException
300
	 */
301
	public function verifyIdentBasedOnItsType(&$ident, $type, string $instance = '') {
302
		if ($instance === $this->configService->getLocalCloudId()) {
303
			$instance = '';
304
		}
305
306
		$this->verifyIdentLocalMember($ident, $type, $instance);
307
		$this->verifyIdentEmailAddress($ident, $type);
308
		$this->verifyIdentContact($ident, $type);
309
	}
310
311
312
	/**
313
	 * Verify if a local account is valid.
314
	 *
315
	 * @param $ident
316
	 * @param $type
317
	 *
318
	 * @param string $instance
319
	 *
320
	 * @throws NoUserException
321
	 */
322
	private function verifyIdentLocalMember(&$ident, $type, string $instance = '') {
323
		if ($type !== Member::TYPE_USER) {
324
			return;
325
		}
326
327
		if ($instance === '') {
328
			try {
329
				$ident = $this->miscService->getRealUserId($ident);
330
			} catch (NoUserException $e) {
0 ignored issues
show
Bug introduced by
The class OC\User\NoUserException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
331
				throw new NoUserException($this->l10n->t("This user does not exist"));
332
			}
333
		}
334
	}
335
336
337
	/**
338
	 * Verify if a mail have a valid format.
339
	 *
340
	 * @param $ident
341
	 * @param $type
342
	 *
343
	 * @throws EmailAccountInvalidFormatException
344
	 */
345
	private function verifyIdentEmailAddress(&$ident, $type) {
346
347
		if ($type !== Member::TYPE_MAIL) {
348
			return;
349
		}
350
351
		if ($this->configService->isAccountOnly()) {
352
			throw new EmailAccountInvalidFormatException(
353
				$this->l10n->t('You cannot add a mail address as member of your Circle')
354
			);
355
		}
356
357
		if (!filter_var($ident, FILTER_VALIDATE_EMAIL)) {
358
			throw new EmailAccountInvalidFormatException(
359
				$this->l10n->t('Email format is not valid')
360
			);
361
		}
362
	}
363
364
365
	/**
366
	 * Verify if a contact exist in current user address books.
367
	 *
368
	 * @param $ident
369
	 * @param $type
370
	 *
371
	 * @throws NoUserException
372
	 * @throws EmailAccountInvalidFormatException
373
	 */
374
	private function verifyIdentContact(&$ident, $type) {
375
		if ($type !== Member::TYPE_CONTACT) {
376
			return;
377
		}
378
379
		if ($this->configService->isAccountOnly()) {
380
			throw new EmailAccountInvalidFormatException(
381
				$this->l10n->t('You cannot add a contact as member of your Circle')
382
			);
383
		}
384
385
		$tmpContact = $this->userId . ':' . $ident;
386
		$result = MiscService::getContactData($tmpContact);
387
		if (empty($result)) {
388
			throw new NoUserException($this->l10n->t("This contact is not available"));
389
		}
390
391
		$ident = $tmpContact;
392
	}
393
394
395
	/**
396
	 * @param Circle $circle
397
	 * @param string $groupId
398
	 *
399
	 * @return bool
400
	 * @throws Exception
401
	 */
402
	private function addGroupMembers(Circle $circle, $groupId) {
403
404
		$group = OC::$server->getGroupManager()
405
							->get($groupId);
406
		if ($group === null) {
407
			throw new GroupDoesNotExistException($this->l10n->t('This group does not exist'));
408
		}
409
410
		foreach ($group->getUsers() as $user) {
411
			try {
412
				$this->addSingleMember($circle, $user->getUID(), Member::TYPE_USER);
413
			} catch (MemberAlreadyExistsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
414
			} catch (Exception $e) {
415
				throw $e;
416
			}
417
		}
418
419
		return true;
420
	}
421
422
423
	/**
424
	 * // TODO - check this on GS setup
425
	 * @param Circle $circle
426
	 * @param string $mails
427
	 *
428
	 * @return bool
429
	 */
430
	private function addMassiveMails(Circle $circle, $mails) {
431
432
		$mails = trim($mails);
433
		if (substr($mails, 0, 6) !== 'mails:') {
434
			return false;
435
		}
436
437
		$mails = substr($mails, 6);
438
		foreach (explode(' ', $mails) as $mail) {
439
			if (!filter_var($mail, FILTER_VALIDATE_EMAIL)) {
440
				continue;
441
			}
442
443
			try {
444
				$this->addMember($circle->getUniqueId(), $mail, Member::TYPE_MAIL);
0 ignored issues
show
Bug introduced by
The call to addMember() misses a required argument $instance.

This check looks for function calls that miss required arguments.

Loading history...
445
			} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
446
			}
447
		}
448
449
		return true;
450
	}
451
452
453
	/**
454
	 * getMember();
455
	 *
456
	 * Will return any data of a user related to a circle (as a Member). User can be a 'non-member'
457
	 * Viewer needs to be at least Member of the Circle
458
	 *
459
	 * @param $circleId
460
	 * @param $userId
461
	 * @param $type
462
	 * @param bool $forceAll
463
	 *
464
	 * @return Member
465
	 * @throws CircleDoesNotExistException
466
	 * @throws ConfigNoCircleAvailableException
467
	 * @throws MemberDoesNotExistException
468
	 */
469
	public function getMember($circleId, $userId, $type, $forceAll = false) {
470
		if (!$forceAll) {
471
			$this->circlesRequest->getCircle($circleId, $this->userId)
472
								 ->getHigherViewer()
473
								 ->hasToBeMember();
474
		}
475
476
		$member = $this->membersRequest->forceGetMember($circleId, $userId, $type);
477
		$member->setNote('');
478
479
		return $member;
480
	}
481
482
483
	/**
484
	 * @param string $memberId
485
	 *
486
	 * @return Member
487
	 * @throws MemberDoesNotExistException
488
	 */
489
	public function getMemberById(string $memberId): Member {
490
		return $this->membersRequest->forceGetMemberById($memberId);
491
	}
492
493
494
	/**
495
	 * @param string $circleUniqueId
496
	 * @param string $name
497
	 * @param int $type
498
	 * @param string $instance
499
	 * @param int $level
500
	 * @param bool $force
501
	 *
502
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use Member[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
503
	 * @throws CircleDoesNotExistException
504
	 * @throws CircleTypeNotValidException
505
	 * @throws ConfigNoCircleAvailableException
506
	 * @throws MemberDoesNotExistException
507
	 * @throws Exception
508
	 */
509
	public function levelMember(
510
		string $circleUniqueId, string $name, int $type, string $instance, int $level, bool $force = false
511
	) {
512
		$level = (int)$level;
513
		if ($force === false) {
514
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
515
		} else {
516
			$circle = $this->circlesRequest->forceGetCircle($circleUniqueId);
517
		}
518
519
		if ($circle->getType() === Circle::CIRCLES_PERSONAL) {
520
			throw new CircleTypeNotValidException(
521
				$this->l10n->t('You cannot edit level in a personal circle')
522
			);
523
		}
524
525
		$member = $this->membersRequest->forceGetMember($circle->getUniqueId(), $name, $type, $instance);
526
		if ($member->getLevel() !== $level) {
527
			$event = new GSEvent(GSEvent::MEMBER_LEVEL, false, $force);
528
			$event->setCircle($circle);
529
530
			$event->getData()
531
				  ->sInt('level', $level);
532
			$event->setMember($member);
533
			$this->gsUpstreamService->newEvent($event);
534
		}
535
536 View Code Duplication
		if ($force === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
537
			return $this->membersRequest->getMembers(
538
				$circle->getUniqueId(), $circle->getHigherViewer()
539
			);
540
		} else {
541
			return $this->membersRequest->forceGetMembers($circle->getUniqueId());
542
		}
543
544
	}
545
546
547
	/**
548
	 * @param string $circleUniqueId
549
	 * @param string $name
550
	 * @param int $type
551
	 * @param string $instance
552
	 * @param bool $force
553
	 *
554
	 * @return Member[]
555
	 * @throws CircleDoesNotExistException
556
	 * @throws ConfigNoCircleAvailableException
557
	 * @throws MemberDoesNotExistException
558
	 * @throws MemberIsNotModeratorException
559
	 * @throws Exception
560
	 */
561
	public function removeMember(
562
		string $circleUniqueId, string $name, int $type, string $instance, bool $force = false
563
	): array {
564
565 View Code Duplication
		if ($force === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
566
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
567
			$circle->getHigherViewer()
568
				   ->hasToBeModerator();
569
		} else {
570
			$circle = $this->circlesRequest->forceGetCircle($circleUniqueId);
571
		}
572
573
		$member = $this->membersRequest->forceGetMember($circleUniqueId, $name, $type, $instance);
574
575
		$event = new GSEvent(GSEvent::MEMBER_REMOVE, false, $force);
576
		$event->setCircle($circle);
577
		$event->setMember($member);
578
		$this->gsUpstreamService->newEvent($event);
579
580 View Code Duplication
		if ($force === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
581
			return $this->membersRequest->getMembers(
582
				$circle->getUniqueId(), $circle->getHigherViewer()
583
			);
584
		} else {
585
			return $this->membersRequest->forceGetMembers($circle->getUniqueId());
586
		}
587
	}
588
589
590
	/**
591
	 * // TODO - check this on GS setup
592
	 * When a user is removed, remove him from all Circles
593
	 *
594
	 * @param $userId
595
	 */
596
	public function onUserRemoved($userId) {
597
		// TODO: broadcast the event to all instances
598
		$this->membersRequest->removeAllMembershipsFromUser($userId);
599
	}
600
601
602
}
603
604