Completed
Pull Request — master (#141)
by Maxence
02:40
created

getSharedWithCircleMembers()   C

Complexity

Conditions 7
Paths 8

Size

Total Lines 34
Code Lines 20

Duplication

Lines 14
Ratio 41.18 %

Importance

Changes 0
Metric Value
dl 14
loc 34
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 20
nc 8
nop 5
1
<?php
2
/**
3
 * Circles - Bring cloud-users closer together.
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
 * @author Vinicius Cubas Brand <[email protected]>
10
 * @author Daniel Tygel <[email protected]>
11
 *
12
 * @copyright 2017
13
 * @license GNU AGPL version 3 or any later version
14
 *
15
 * This program is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License as
17
 * published by the Free Software Foundation, either version 3 of the
18
 * License, or (at your option) any later version.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
27
 *
28
 */
29
30
31
namespace OCA\Circles;
32
33
34
use OC\Files\Cache\Cache;
35
use OC\Share20\Exception\InvalidShare;
36
use OC\Share20\Share;
37
use OCA\Circles\Api\v1\Circles;
38
use OCA\Circles\AppInfo\Application;
39
use OCA\Circles\Db\CircleProviderRequest;
40
use OCA\Circles\Db\CirclesRequest;
41
use OCA\Circles\Db\MembersRequest;
42
use OCA\Circles\Model\Circle;
43
use OCA\Circles\Model\Member;
44
use OCA\Circles\Service\CirclesService;
45
use OCA\Circles\Service\ConfigService;
46
use OCA\Circles\Service\MiscService;
47
use OCP\DB\QueryBuilder\IQueryBuilder;
48
use OCP\Files\Folder;
49
use OCP\Files\IRootFolder;
50
use OCP\Files\Node;
51
use OCP\IDBConnection;
52
use OCP\IL10N;
53
use OCP\ILogger;
54
use OCP\IURLGenerator;
55
use OCP\IUserManager;
56
use OCP\Security\ISecureRandom;
57
use OCP\Share\Exceptions\ShareNotFound;
58
use OCP\Share\IShare;
59
use OCP\Share\IShareProvider;
60
61
62
class ShareByCircleProvider extends CircleProviderRequest implements IShareProvider {
63
64
	/** @var ILogger */
65
	private $logger;
66
67
	/** @var ISecureRandom */
68
	private $secureRandom;
69
70
	/** @var IUserManager */
71
	private $userManager;
72
73
	/** @var IRootFolder */
74
	private $rootFolder;
75
76
	/** @var IURLGenerator */
77
	private $urlGenerator;
78
79
	/** @var CirclesRequest */
80
	private $circlesRequest;
81
82
	/** @var MembersRequest */
83
	private $membersRequest;
84
85
86
	/**
87
	 * DefaultShareProvider constructor.
88
	 *
89
	 * @param IDBConnection $connection
90
	 * @param ISecureRandom $secureRandom
91
	 * @param IUserManager $userManager
92
	 * @param IRootFolder $rootFolder
93
	 * @param IL10N $l10n
94
	 * @param ILogger $logger
95
	 * @param IURLGenerator $urlGenerator
96
	 */
97
	public function __construct(
98
		IDBConnection $connection, ISecureRandom $secureRandom, IUserManager $userManager,
99
		IRootFolder $rootFolder, IL10N $l10n, ILogger $logger, IURLGenerator $urlGenerator
100
	) {
101
		$app = new Application();
102
		$container = $app->getContainer();
103
		$configService = $container->query(ConfigService::class);
104
		$miscService = $container->query(MiscService::class);
105
106
		parent::__construct($l10n, $connection, $configService, $miscService);
107
108
		$this->secureRandom = $secureRandom;
109
		$this->userManager = $userManager;
110
		$this->rootFolder = $rootFolder;
111
		$this->logger = $logger;
112
		$this->urlGenerator = $urlGenerator;
113
		$this->circlesRequest = $container->query(CirclesRequest::class);
114
		$this->membersRequest = $container->query(MembersRequest::class);
115
	}
116
117
118
	/**
119
	 * Return the identifier of this provider.
120
	 *
121
	 * @return string
122
	 */
123
	public function identifier() {
124
		return 'ocCircleShare';
125
	}
126
127
128
	/**
129
	 * Create a share if it does not exist already.
130
	 *
131
	 * @param IShare $share
132
	 *
133
	 * @return IShare The share object
134
	 * @throws \Exception
135
	 */
136
	public function create(IShare $share) {
137
		try {
138
			$nodeId = $share->getNode()
139
							->getId();
140
141
			$qb = $this->findShareParentSql($nodeId, $share->getSharedWith());
142
			$exists = $qb->execute();
143
			$data = $exists->fetch();
144
			$exists->closeCursor();
145
146
			if ($data !== false) {
147
				throw $this->errorShareAlreadyExist($share);
148
			}
149
150
			$share->setToken(substr(bin2hex(openssl_random_pseudo_bytes(24)), 1, 15));
151
			$shareId = $this->createShare($share);
152
153
			$circle =
154
				$this->circlesRequest->getCircle($share->getSharedWith(), $share->getSharedby());
155
			$circle->getHigherViewer()
156
				   ->hasToBeMember();
157
158
			Circles::shareToCircle(
159
				$circle->getUniqueId(), 'files', '',
160
				['id' => $shareId, 'share' => $this->shareObjectToArray($share)],
161
				'\OCA\Circles\Circles\FileSharingBroadcaster'
162
			);
163
164
			return $this->getShareById($shareId);
165
		} catch (\Exception $e) {
166
			throw $e;
167
		}
168
	}
169
170
171
	/**
172
	 * Update a share
173
	 * permissions right, owner and initiator
174
	 *
175
	 * @param IShare $share
176
	 *
177
	 * @return IShare The share object
178
	 */
179
	public function update(IShare $share) {
180
181
		$qb = $this->getBaseUpdateSql();
182
		$this->limitToShare($qb, $share->getId());
183
		$qb->set('permissions', $qb->createNamedParameter($share->getPermissions()))
184
		   ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
185
		   ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()));
186
		$qb->execute();
187
188
		return $share;
189
	}
190
191
192
	/**
193
	 * Delete a share, and it's children
194
	 *
195
	 * @param IShare $share
196
	 */
197
	public function delete(IShare $share) {
198
199
		$qb = $this->getBaseDeleteSql();
200
		$this->limitToShareAndChildren($qb, $share->getId());
201
202
		$qb->execute();
203
	}
204
205
206
	/**
207
	 * Unshare a file from self as recipient.
208
	 * Because every circles share are group shares, we will set permissions to 0
209
	 *
210
	 * @param IShare $share
211
	 * @param string $userId
212
	 */
213 View Code Duplication
	public function deleteFromSelf(IShare $share, $userId) {
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...
214
		$childId = $this->getShareChildId($share, $userId);
215
216
		$qb = $this->getBaseUpdateSql();
217
		$qb->set('permissions', $qb->createNamedParameter(0));
218
		$this->limitToShare($qb, $childId);
219
220
		$qb->execute();
221
	}
222
223
224
	/**
225
	 * Move a share as a recipient.
226
	 *
227
	 * @param IShare $share
228
	 * @param string $userId
229
	 *
230
	 * @return IShare
231
	 *
232
	 */
233 View Code Duplication
	public function move(IShare $share, $userId) {
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...
234
235
		$childId = $this->getShareChildId($share, $userId);
236
237
		$qb = $this->getBaseUpdateSql();
238
		$qb->set('file_target', $qb->createNamedParameter($share->getTarget()));
239
		$this->limitToShare($qb, $childId);
240
		$qb->execute();
241
242
		return $share;
243
	}
244
245
246
	/**
247
	 * return the child ID of a share
248
	 *
249
	 * @param IShare $share
250
	 * @param string $userId
251
	 *
252
	 * @return bool
253
	 */
254
	private function getShareChildId(IShare $share, $userId) {
255
		$qb = $this->getBaseSelectSql($share->getId());
256
		$this->limitToShareChildren($qb, $userId, $share->getId());
257
258
		$child = $qb->execute();
259
		$data = $child->fetch();
260
		$child->closeCursor();
261
262
		if ($data === false) {
263
			return $this->createShareChild($userId, $share);
264
		}
265
266
		return $data['id'];
267
	}
268
269
270
	/**
271
	 * Create a child and returns its ID
272
	 *
273
	 * @param IShare $share
274
	 *
275
	 * @return int
276
	 */
277
	private function createShare($share) {
278
		$qb = $this->getBaseInsertSql($share);
279
		$qb->execute();
280
		$id = $qb->getLastInsertId();
281
282
		return (int)$id;
283
	}
284
285
286
	/**
287
	 * Create a child and returns its ID
288
	 *
289
	 * @param string $userId
290
	 * @param IShare $share
291
	 *
292
	 * @return int
293
	 */
294
	private function createShareChild($userId, $share) {
295
		$qb = $this->getBaseInsertSql($share);
296
297
		$qb->setValue('parent', $qb->createNamedParameter($share->getId()));
298
		$qb->setValue('share_with', $qb->createNamedParameter($userId));
299
		$qb->execute();
300
		$id = $qb->getLastInsertId();
301
302
		return (int)$id;
303
	}
304
305
306
	/**
307
	 * Get all shares by the given user in a folder
308
	 *
309
	 * @param string $userId
310
	 * @param Folder $node
311
	 * @param bool $reshares Also get the shares where $user is the owner instead of just the
312
	 *     shares where $user is the initiator
313
	 *
314
	 * @return Share[]
315
	 */
316
	public function getSharesInFolder($userId, Folder $node, $reshares) {
317
		$qb = $this->getBaseSelectSql();
318
		$this->limitToShareOwner($qb, $userId, true);
319
		$cursor = $qb->execute();
320
321
		$shares = [];
322
		while ($data = $cursor->fetch()) {
323
			$shares[$data['file_source']][] = $this->createShareObject($data);
324
		}
325
		$cursor->closeCursor();
326
327
		return $shares;
328
	}
329
330
331
	/**
332
	 * Get all shares by the given user
333
	 *
334
	 * @param string $userId
335
	 * @param int $shareType
336
	 * @param Node|null $node
337
	 * @param bool $reShares
338
	 * @param int $limit The maximum number of shares to be returned, -1 for all shares
339
	 * @param int $offset
340
	 *
341
	 * @return Share[]
342
	 */
343
	public function getSharesBy($userId, $shareType, $node, $reShares, $limit, $offset) {
344
		$qb = $this->getBaseSelectSql();
345
		$this->limitToShareOwner($qb, $userId, $reShares);
346
347
		if ($node !== null) {
348
			$this->limitToFiles($qb, $node->getId());
349
		}
350
351
		$this->limitToPage($qb, $limit, $offset);
352
		$cursor = $qb->execute();
353
354
		$shares = [];
355
		while ($data = $cursor->fetch()) {
356
			$shares[] = $this->createShareObject($this->editShareEntry($data));
357
		}
358
		$cursor->closeCursor();
359
360
		return $shares;
361
	}
362
363
364
	/**
365
	 * returns a better formatted string to display more information about
366
	 * the circle to the Sharing UI
367
	 *
368
	 * @param $data
369
	 *
370
	 * @return array<string,string>
371
	 */
372
	private function editShareEntry($data) {
373
		$data['share_with'] =
374
			sprintf(
375
				'%s (%s, %s)', $data['circle_name'], Circle::TypeLongString($data['circle_type']),
376
				$this->miscService->getDisplayName($data['circle_owner'])
377
			);
378
379
		return $data;
380
	}
381
382
383
	/**
384
	 * Get share by its id
385
	 *
386
	 * @param int $shareId
387
	 * @param string|null $recipientId
388
	 *
389
	 * @return Share
0 ignored issues
show
Documentation introduced by
Should the return type not be IShare?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
390
	 * @throws ShareNotFound
391
	 */
392
	public function getShareById($shareId, $recipientId = null) {
393
		$qb = $this->getBaseSelectSql();
394
395
		$this->limitToShare($qb, $shareId);
396
397
		$cursor = $qb->execute();
398
		$data = $cursor->fetch();
399
		$cursor->closeCursor();
400
401
		if ($data === false) {
402
			throw new ShareNotFound();
403
		}
404
405
		return $this->createShareObject($data);
406
	}
407
408
409
	/**
410
	 * Get shares for a given path
411
	 *
412
	 * @param Node $path
413
	 *
414
	 * @return IShare[]|null
415
	 */
416
	public function getSharesByPath(Node $path) {
417
		return null;
418
	}
419
420
421
	/**
422
	 * Get shared with the given user
423
	 *
424
	 * @param string $userId get shares where this user is the recipient
425
	 * @param int $shareType
426
	 * @param Node|null $node
427
	 * @param int $limit The max number of entries returned, -1 for all
428
	 * @param int $offset
429
	 *
430
	 * @return IShare[]
431
	 */
432
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
433
434
		$shares = $this->getSharedWithCircleMembers($userId, $shareType, $node, $limit, $offset);
435
436
		return $shares;
437
	}
438
439
440
	/**
441
	 * @param string $userId
442
	 * @param $shareType
443
	 * @param Node $node
444
	 * @param int $limit
445
	 * @param int $offset
446
	 *
447
	 * @return IShare[]
448
	 */
449
	private function getSharedWithCircleMembers($userId, $shareType, $node, $limit, $offset) {
450
451
		$qb = $this->getCompleteSelectSql();
452
		$this->linkToFileCache($qb, $userId);
453
		$this->limitToPage($qb, $limit, $offset);
454
455
		if ($node !== null) {
456
			$this->limitToFiles($qb, [$node->getId()]);
457
		}
458
459
		$this->linkToMember($qb, $userId, $this->configService->isLinkedGroupsAllowed());
460
461
		$this->leftJoinShareInitiator($qb);
462
		$cursor = $qb->execute();
463
464
		$shares = [];
465 View Code Duplication
		while ($data = $cursor->fetch()) {
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...
466
467
			if ($data['initiator_circle_level'] < Member::LEVEL_MEMBER
468
				&& ($data['initiator_group_level'] < Member::LEVEL_MEMBER
469
					|| !$this->configService->isLinkedGroupsAllowed())
470
			) {
471
				continue;
472
			}
473
474
			self::editShareFromParentEntry($data);
475
			if (self::isAccessibleResult($data)) {
476
				$shares[] = $this->createShareObject($data);
477
			}
478
		}
479
		$cursor->closeCursor();
480
481
		return $shares;
482
	}
483
484
485
	/**
486
	 * Get a share by token
487
	 *
488
	 * @param string $token
489
	 *
490
	 * @return IShare
491
	 * @throws ShareNotFound
492
	 * @deprecated - use local querybuilder lib instead
493
	 */
494
	public function getShareByToken($token) {
495
		$qb = $this->dbConnection->getQueryBuilder();
496
497
		$cursor = $qb->select('*')
498
					 ->from('share')
499
					 ->where(
500
						 $qb->expr()
501
							->eq(
502
								'share_type',
503
								$qb->createNamedParameter(\OCP\Share::SHARE_TYPE_CIRCLE)
504
							)
505
					 )
506
					 ->andWhere(
507
						 $qb->expr()
508
							->eq('token', $qb->createNamedParameter($token))
509
					 )
510
					 ->execute();
511
512
		$data = $cursor->fetch();
513
514
		if ($data === false) {
515
			throw new ShareNotFound('Share not found', $this->l10n->t('Could not find share'));
516
		}
517
518
		try {
519
			$share = $this->createShareObject($data);
520
		} catch (InvalidShare $e) {
0 ignored issues
show
Bug introduced by
The class OC\Share20\Exception\InvalidShare 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...
521
			throw new ShareNotFound('Share not found', $this->l10n->t('Could not find share'));
522
		}
523
524
		return $share;
525
	}
526
527
528
	/**
529
	 * We don't return a thing about children.
530
	 * The call to this function is deprecated and should be removed in next release of NC.
531
	 * Also, we get the children in the delete() method.
532
	 *
533
	 * @param IShare $parent
534
	 *
535
	 * @return array
536
	 */
537
	public function getChildren(IShare $parent) {
538
		return [];
539
	}
540
541
542
	/**
543
	 * A user is deleted from the system
544
	 * So clean up the relevant shares.
545
	 *
546
	 * @param string $uid
547
	 * @param int $shareType
548
	 */
549
	public function userDeleted($uid, $shareType) {
550
		// TODO: Implement userDeleted() method.
551
	}
552
553
554
	/**
555
	 * A group is deleted from the system.
556
	 * We handle our own groups.
557
	 *
558
	 * @param string $gid
559
	 */
560
	public function groupDeleted($gid) {
561
		return;
562
	}
563
564
565
	/**
566
	 * A user is deleted from a group.
567
	 * We handle our own groups.
568
	 *
569
	 * @param string $uid
570
	 * @param string $gid
571
	 */
572
	public function userDeletedFromGroup($uid, $gid) {
573
		return;
574
	}
575
576
577
	/**
578
	 * Create a share object
579
	 *
580
	 * @param array $data
581
	 *
582
	 * @return IShare
583
	 */
584
	private function createShareObject($data) {
585
586
		$share = new Share($this->rootFolder, $this->userManager);
587
		$share->setId((int)$data['id'])
588
			  ->setPermissions((int)$data['permissions'])
589
			  ->setNodeType($data['item_type']);
590
591
		$share->setNodeId((int)$data['file_source'])
592
			  ->setTarget($data['file_target']);
593
594
		$this->assignShareObjectSharesProperties($share, $data);
595
		$this->assignShareObjectPropertiesFromParent($share, $data);
596
597
		$share->setProviderId($this->identifier());
598
599
		return $share;
600
	}
601
602
603
	/**
604
	 * @param IShare $share
605
	 * @param $data
606
	 */
607
	private function assignShareObjectPropertiesFromParent(IShare &$share, $data) {
608
		if (isset($data['f_permissions'])) {
609
			$entryData = $data;
610
			$entryData['permissions'] = $entryData['f_permissions'];
611
			$entryData['parent'] = $entryData['f_parent'];
612
			$share->setNodeCacheEntry(
613
				Cache::cacheEntryFromData(
614
					$entryData,
615
					\OC::$server->getMimeTypeLoader()
616
				)
617
			);
618
		}
619
	}
620
621
622
	/**
623
	 * @param IShare $share
624
	 * @param $data
625
	 */
626
	private function assignShareObjectSharesProperties(IShare &$share, $data) {
627
		$shareTime = new \DateTime();
628
		$shareTime->setTimestamp((int)$data['stime']);
629
630
		$share->setShareTime($shareTime);
631
		$share->setSharedWith($data['share_with'])
632
			  ->setSharedBy($data['uid_initiator'])
633
			  ->setShareOwner($data['uid_owner'])
634
			  ->setShareType((int)$data['share_type']);
635
636
		if (method_exists($share, 'setSharedWithDisplayName')) {
637
			$share->setSharedWithAvatar(CirclesService::getCircleIcon($data['circle_type']))
638
				  ->setSharedWithDisplayName(
639
					  sprintf(
640
						  '%s (%s, %s)', $data['circle_name'],
641
						  Circle::TypeLongString($data['circle_type']),
642
						  $this->miscService->getDisplayName($data['circle_owner'])
643
					  )
644
				  );
645
		}
646
	}
647
648
649
	/**
650
	 * @param IShare $share
651
	 *
652
	 * @return \Exception
653
	 */
654
	private function errorShareAlreadyExist($share) {
655
		$share_src = $share->getNode()
656
						   ->getName();
657
658
		$message = 'Sharing %s failed, this item is already shared with this circle';
659
		$message_t = $this->l10n->t($message, array($share_src));
660
		$this->logger->debug(
661
			sprintf($message, $share_src, $share->getSharedWith()), ['app' => 'circles']
662
		);
663
664
		return new \Exception($message_t);
665
	}
666
667
668
	/**
669
	 * Get the access list to the array of provided nodes.
670
	 *
671
	 * @see IManager::getAccessList() for sample docs
672
	 *
673
	 * @param Node[] $nodes The list of nodes to get access for
674
	 * @param bool $currentAccess If current access is required (like for removed shares that might
675
	 *     get revived later)
676
	 *
677
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array>.

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...
678
	 * @since 12
679
	 */
680
	public function getAccessList($nodes, $currentAccess) {
681
		$ids = [];
682
		foreach ($nodes as $node) {
683
			$ids[] = $node->getId();
684
		}
685
686
		$qb = $this->getAccessListBaseSelectSql();
687
		$this->limitToFiles($qb, $ids);
688
689
		$users = $this->parseAccessListResult($qb);
690
691
		if ($currentAccess === false) {
692
			$users = array_keys($users);
693
		}
694
695
		return ['users' => $users];
696
	}
697
698
699
	/**
700
	 * return array regarding getAccessList format.
701
	 * ie. \OC\Share20\Manager::getAccessList()
702
	 *
703
	 * @param IQueryBuilder $qb
704
	 *
705
	 * @return array
706
	 */
707
	private function parseAccessListResult(IQueryBuilder $qb) {
708
709
		$cursor = $qb->execute();
710
		$users = [];
711
712
		while ($row = $cursor->fetch()) {
713
			$userId = $row['user_id'];
714
715
			if (!key_exists($userId, $users)) {
716
				$users[$userId] = [
717
					'node_id'   => $row['file_source'],
718
					'node_path' => $row['file_target']
719
				];
720
			}
721
		}
722
		$cursor->closeCursor();
723
724
		return $users;
725
	}
726
727
728
	/**
729
	 * @param IShare $share
730
	 *
731
	 * @return array
732
	 */
733
	private function shareObjectToArray(IShare $share) {
734
		return [
735
			'sharedWith'  => $share->getSharedWith(),
736
			'sharedBy'    => $share->getSharedBy(),
737
			'nodeId'      => $share->getNodeId(),
738
			'shareOwner'  => $share->getShareOwner(),
739
			'permissions' => $share->getPermissions(),
740
			'token'       => $share->getToken(),
741
			'password'    => $share->getPassword()
742
		];
743
	}
744
}
745