Completed
Pull Request — master (#362)
by Maxence
01:55 queued 11s
created

MembersService::verifyIdentContact()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
cc 4
nc 4
nop 2
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
	 * @throws Exception
184
	 */
185
	private function addSingleMember(Circle $circle, $ident, $type, $instance = '', bool $force = false) {
186
		$this->verifyIdentBasedOnItsType($ident, $type, $instance);
187
		$this->verifyIdentContact($ident, $type);
188
189
		$member = $this->membersRequest->getFreshNewMember($circle->getUniqueId(), $ident, $type, $instance);
190
191
		$event = new GSEvent(GSEvent::MEMBER_ADD, false, $force);
192
		$event->setSeverity(GSEvent::SEVERITY_HIGH);
193
194
		$event->setCircle($circle);
195
		$event->setMember($member);
196
		$this->gsUpstreamService->newEvent($event);
197
	}
198
199
200
	/**
201
	 * add a bunch of users to a circle based on the type of the 'bunch'
202
	 *
203
	 * @param Circle $circle
204
	 * @param string $ident
205
	 * @param int $type
206
	 *
207
	 * @return bool
208
	 * @throws Exception
209
	 */
210
	private function addMassiveMembers(Circle $circle, $ident, $type) {
211
		if ($type === Member::TYPE_GROUP) {
212
			return $this->addGroupMembers($circle, $ident);
213
		}
214
215
		if ($type === Member::TYPE_USER) {
216
			return $this->addMassiveMails($circle, $ident);
217
		}
218
219
		return false;
220
	}
221
222
223
	/**
224
	 * add a new member based on its type.
225
	 *
226
	 * @param Circle $circle
227
	 * @param Member $member
228
	 *
229
	 * @throws CircleTypeNotValidException
230
	 * @throws MemberCantJoinCircleException
231
	 */
232
	public function addMemberBasedOnItsType(Circle $circle, Member &$member) {
233
		$this->addLocalMember($circle, $member);
234
		$this->addEmailAddress($member);
235
		$this->addContact($member);
236
	}
237
238
239
	/**
240
	 * @param Circle $circle
241
	 * @param Member $member
242
	 *
243
	 * @throws CircleTypeNotValidException
244
	 * @throws MemberCantJoinCircleException
245
	 */
246
	private function addLocalMember(Circle $circle, Member $member) {
247
248
		if ($member->getType() !== Member::TYPE_USER) {
249
			return;
250
		}
251
252
		$member->inviteToCircle($circle->getType());
253
254
		if ($circle->getType() === Circle::CIRCLES_CLOSED && $this->configService->isInvitationSkipped()) {
255
			$member->joinCircle($circle->getType());
256
		}
257
	}
258
259
260
	/**
261
	 * add mail address as contact.
262
	 *
263
	 * @param Member $member
264
	 */
265
	private function addEmailAddress(Member $member) {
266
267
		if ($member->getType() !== Member::TYPE_MAIL) {
268
			return;
269
		}
270
271
		$member->addMemberToCircle();
272
	}
273
274
275
	/**
276
	 * // TODO - check this on GS setup
277
	 * Add contact as member.
278
	 *
279
	 * @param Member $member
280
	 */
281
	private function addContact(Member $member) {
282
283
		if ($member->getType() !== Member::TYPE_CONTACT) {
284
			return;
285
		}
286
287
		$member->addMemberToCircle();
288
	}
289
290
291
	/**
292
	 * // TODO - check this on GS setup
293
	 * Verify the availability of an ident, based on its type.
294
	 *
295
	 * @param string $ident
296
	 * @param int $type
297
	 * @param string $instance
298
	 *
299
	 * @throws EmailAccountInvalidFormatException
300
	 * @throws NoUserException
301
	 */
302
	public function verifyIdentBasedOnItsType(&$ident, $type, string $instance = '') {
303
		if ($instance === $this->configService->getLocalCloudId()) {
304
			$instance = '';
305
		}
306
307
		$this->verifyIdentLocalMember($ident, $type, $instance);
308
		$this->verifyIdentEmailAddress($ident, $type);
309
//		$this->verifyIdentContact($ident, $type);
310
	}
311
312
313
	/**
314
	 * Verify if a local account is valid.
315
	 *
316
	 * @param $ident
317
	 * @param $type
318
	 *
319
	 * @param string $instance
320
	 *
321
	 * @throws NoUserException
322
	 */
323
	private function verifyIdentLocalMember(&$ident, $type, string $instance = '') {
324
		if ($type !== Member::TYPE_USER) {
325
			return;
326
		}
327
328
		if ($instance === '') {
329
			try {
330
				$ident = $this->miscService->getRealUserId($ident);
331
			} 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...
332
				throw new NoUserException($this->l10n->t("This user does not exist"));
333
			}
334
		}
335
	}
336
337
338
	/**
339
	 * Verify if a mail have a valid format.
340
	 *
341
	 * @param $ident
342
	 * @param $type
343
	 *
344
	 * @throws EmailAccountInvalidFormatException
345
	 */
346
	private function verifyIdentEmailAddress(&$ident, $type) {
347
348
		if ($type !== Member::TYPE_MAIL) {
349
			return;
350
		}
351
352
		if ($this->configService->isAccountOnly()) {
353
			throw new EmailAccountInvalidFormatException(
354
				$this->l10n->t('You cannot add a mail address as member of your Circle')
355
			);
356
		}
357
358
		if (!filter_var($ident, FILTER_VALIDATE_EMAIL)) {
359
			throw new EmailAccountInvalidFormatException(
360
				$this->l10n->t('Email format is not valid')
361
			);
362
		}
363
	}
364
365
366
	/**
367
	 * Verify if a contact exist in current user address books.
368
	 *
369
	 * @param $ident
370
	 * @param $type
371
	 *
372
	 * @throws NoUserException
373
	 * @throws EmailAccountInvalidFormatException
374
	 */
375
	private function verifyIdentContact(&$ident, $type) {
376
		if ($type !== Member::TYPE_CONTACT) {
377
			return;
378
		}
379
380
		if ($this->configService->isAccountOnly()) {
381
			throw new EmailAccountInvalidFormatException(
382
				$this->l10n->t('You cannot add a contact as member of your Circle')
383
			);
384
		}
385
386
		$tmpContact = $this->userId . ':' . $ident;
387
		$result = MiscService::getContactData($tmpContact);
388
		if (empty($result)) {
389
			throw new NoUserException($this->l10n->t("This contact is not available"));
390
		}
391
392
		$ident = $tmpContact;
393
	}
394
395
396
	/**
397
	 * @param Circle $circle
398
	 * @param string $groupId
399
	 *
400
	 * @return bool
401
	 * @throws Exception
402
	 */
403
	private function addGroupMembers(Circle $circle, $groupId) {
404
405
		$group = OC::$server->getGroupManager()
406
							->get($groupId);
407
		if ($group === null) {
408
			throw new GroupDoesNotExistException($this->l10n->t('This group does not exist'));
409
		}
410
411
		foreach ($group->getUsers() as $user) {
412
			try {
413
				$this->addSingleMember($circle, $user->getUID(), Member::TYPE_USER);
414
			} catch (MemberAlreadyExistsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
415
			} catch (Exception $e) {
416
				throw $e;
417
			}
418
		}
419
420
		return true;
421
	}
422
423
424
	/**
425
	 * // TODO - check this on GS setup
426
	 *
427
	 * @param Circle $circle
428
	 * @param string $mails
429
	 *
430
	 * @return bool
431
	 */
432
	private function addMassiveMails(Circle $circle, $mails) {
433
434
		$mails = trim($mails);
435
		if (substr($mails, 0, 6) !== 'mails:') {
436
			return false;
437
		}
438
439
		$mails = substr($mails, 6);
440
		foreach (explode(' ', $mails) as $mail) {
441
			if (!filter_var($mail, FILTER_VALIDATE_EMAIL)) {
442
				continue;
443
			}
444
445
			try {
446
				$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...
447
			} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
448
			}
449
		}
450
451
		return true;
452
	}
453
454
455
	/**
456
	 * getMember();
457
	 *
458
	 * Will return any data of a user related to a circle (as a Member). User can be a 'non-member'
459
	 * Viewer needs to be at least Member of the Circle
460
	 *
461
	 * @param $circleId
462
	 * @param $userId
463
	 * @param $type
464
	 * @param bool $forceAll
465
	 *
466
	 * @return Member
467
	 * @throws CircleDoesNotExistException
468
	 * @throws ConfigNoCircleAvailableException
469
	 * @throws MemberDoesNotExistException
470
	 */
471
	public function getMember($circleId, $userId, $type, $forceAll = false) {
472
		if (!$forceAll) {
473
			$this->circlesRequest->getCircle($circleId, $this->userId)
474
								 ->getHigherViewer()
475
								 ->hasToBeMember();
476
		}
477
478
		$member = $this->membersRequest->forceGetMember($circleId, $userId, $type);
479
		$member->setNote('');
480
481
		return $member;
482
	}
483
484
485
	/**
486
	 * @param string $memberId
487
	 *
488
	 * @return Member
489
	 * @throws MemberDoesNotExistException
490
	 */
491
	public function getMemberById(string $memberId): Member {
492
		return $this->membersRequest->forceGetMemberById($memberId);
493
	}
494
495
496
	/**
497
	 * @param string $circleUniqueId
498
	 * @param string $name
499
	 * @param int $type
500
	 * @param string $instance
501
	 * @param int $level
502
	 * @param bool $force
503
	 *
504
	 * @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...
505
	 * @throws CircleDoesNotExistException
506
	 * @throws CircleTypeNotValidException
507
	 * @throws ConfigNoCircleAvailableException
508
	 * @throws MemberDoesNotExistException
509
	 * @throws Exception
510
	 */
511
	public function levelMember(
512
		string $circleUniqueId, string $name, int $type, string $instance, int $level, bool $force = false
513
	) {
514
		$level = (int)$level;
515
		if ($force === false) {
516
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
517
		} else {
518
			$circle = $this->circlesRequest->forceGetCircle($circleUniqueId);
519
		}
520
521
		if ($circle->getType() === Circle::CIRCLES_PERSONAL) {
522
			throw new CircleTypeNotValidException(
523
				$this->l10n->t('You cannot edit level in a personal circle')
524
			);
525
		}
526
527
		$member = $this->membersRequest->forceGetMember($circle->getUniqueId(), $name, $type, $instance);
528
		if ($member->getLevel() !== $level) {
529
			$event = new GSEvent(GSEvent::MEMBER_LEVEL, false, $force);
530
			$event->setCircle($circle);
531
532
			$event->getData()
533
				  ->sInt('level', $level);
534
			$event->setMember($member);
535
			$this->gsUpstreamService->newEvent($event);
536
		}
537
538 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...
539
			return $this->membersRequest->getMembers(
540
				$circle->getUniqueId(), $circle->getHigherViewer()
541
			);
542
		} else {
543
			return $this->membersRequest->forceGetMembers($circle->getUniqueId());
544
		}
545
546
	}
547
548
549
	/**
550
	 * @param string $circleUniqueId
551
	 * @param string $name
552
	 * @param int $type
553
	 * @param string $instance
554
	 * @param bool $force
555
	 *
556
	 * @return Member[]
557
	 * @throws CircleDoesNotExistException
558
	 * @throws ConfigNoCircleAvailableException
559
	 * @throws MemberDoesNotExistException
560
	 * @throws MemberIsNotModeratorException
561
	 * @throws Exception
562
	 */
563
	public function removeMember(
564
		string $circleUniqueId, string $name, int $type, string $instance, bool $force = false
565
	): array {
566
567 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...
568
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
569
			$circle->getHigherViewer()
570
				   ->hasToBeModerator();
571
		} else {
572
			$circle = $this->circlesRequest->forceGetCircle($circleUniqueId);
573
		}
574
575
		$member = $this->membersRequest->forceGetMember($circleUniqueId, $name, $type, $instance);
576
577
		$event = new GSEvent(GSEvent::MEMBER_REMOVE, false, $force);
578
		$event->setCircle($circle);
579
		$event->setMember($member);
580
		$this->gsUpstreamService->newEvent($event);
581
582 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...
583
			return $this->membersRequest->getMembers(
584
				$circle->getUniqueId(), $circle->getHigherViewer()
585
			);
586
		} else {
587
			return $this->membersRequest->forceGetMembers($circle->getUniqueId());
588
		}
589
	}
590
591
592
	/**
593
	 * When a user is removed, remove him from all Circles
594
	 *
595
	 * @param string $userId
596
	 *
597
	 * @throws Exception
598
	 */
599
	public function onUserRemoved(string $userId) {
600
		$event = new GSEvent(GSEvent::USER_DELETED, true, true);
601
602
		$member = new Member($userId);
603
		$event->setMember($member);
604
		$event->getData()
605
			  ->s('userId', $userId);
606
607
		$this->gsUpstreamService->newEvent($event);
608
	}
609
610
611
}
612
613