Completed
Push — master ( a32ba7...f7592a )
by Maxence
05:20
created

ShareByCircleProvider::getSharesInFolder()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 13
rs 9.4285
cc 2
eloc 9
nc 2
nop 3
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
0 ignored issues
show
Documentation introduced by
Should the return type not be array? Also, consider making the array more specific, something like array<String>, or String[].

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.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
415
	 */
416
	public function getSharesByPath(Node $path) {
417
		$qb = $this->getBaseSelectSql();
418
		$this->limitToFiles($qb, [$path->getId()]);
419
		$cursor = $qb->execute();
420
421
		$shares = [];
422
		while ($data = $cursor->fetch()) {
423
			$shares[] = $this->createShareObject($data);
424
		}
425
		$cursor->closeCursor();
426
427
		return $shares;
428
	}
429
430
431
	/**
432
	 * Get shared with the given user
433
	 *
434
	 * @param string $userId get shares where this user is the recipient
435
	 * @param int $shareType
436
	 * @param Node|null $node
437
	 * @param int $limit The max number of entries returned, -1 for all
438
	 * @param int $offset
439
	 *
440
	 * @return IShare[]
441
	 */
442
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
443
444
		$shares = $this->getSharedWithCircleMembers($userId, $shareType, $node, $limit, $offset);
445
446
		return $shares;
447
	}
448
449
450
	/**
451
	 * @param string $userId
452
	 * @param $shareType
453
	 * @param Node $node
454
	 * @param int $limit
455
	 * @param int $offset
456
	 *
457
	 * @return IShare[]
458
	 */
459 View Code Duplication
	private function getSharedWithCircleMembers($userId, $shareType, $node, $limit, $offset) {
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...
460
461
		$qb = $this->getCompleteSelectSql();
462
		$this->linkToFileCache($qb, $userId);
463
		$this->limitToPage($qb, $limit, $offset);
464
465
		if ($node !== null) {
466
			$this->limitToFiles($qb, [$node->getId()]);
467
		}
468
469
		$this->linkToMember($qb, $userId, $this->configService->isLinkedGroupsAllowed());
470
471
		$cursor = $qb->execute();
472
473
		$shares = [];
474
		while ($data = $cursor->fetch()) {
475
			self::editShareFromParentEntry($data);
476
			if (self::isAccessibleResult($data)) {
477
				$shares[] = $this->createShareObject($data);
478
			}
479
		}
480
		$cursor->closeCursor();
481
482
		return $shares;
483
	}
484
485
486
	/**
487
	 * Get a share by token
488
	 *
489
	 * @param string $token
490
	 *
491
	 * @return IShare
492
	 * @throws ShareNotFound
493
	 * @deprecated - use local querybuilder lib instead
494
	 */
495
	public function getShareByToken($token) {
496
		$qb = $this->dbConnection->getQueryBuilder();
497
498
		$cursor = $qb->select('*')
499
					 ->from('share')
500
					 ->where(
501
						 $qb->expr()
502
							->eq(
503
								'share_type',
504
								$qb->createNamedParameter(\OCP\Share::SHARE_TYPE_CIRCLE)
505
							)
506
					 )
507
					 ->andWhere(
508
						 $qb->expr()
509
							->eq('token', $qb->createNamedParameter($token))
510
					 )
511
					 ->execute();
512
513
		$data = $cursor->fetch();
514
515
		if ($data === false) {
516
			throw new ShareNotFound('Share not found', $this->l10n->t('Could not find share'));
517
		}
518
519
		try {
520
			$share = $this->createShareObject($data);
521
		} 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...
522
			throw new ShareNotFound('Share not found', $this->l10n->t('Could not find share'));
523
		}
524
525
		return $share;
526
	}
527
528
529
	/**
530
	 * We don't return a thing about children.
531
	 * The call to this function is deprecated and should be removed in next release of NC.
532
	 * Also, we get the children in the delete() method.
533
	 *
534
	 * @param IShare $parent
535
	 *
536
	 * @return array
537
	 */
538
	public function getChildren(IShare $parent) {
539
		return [];
540
	}
541
542
543
	/**
544
	 * A user is deleted from the system
545
	 * So clean up the relevant shares.
546
	 *
547
	 * @param string $uid
548
	 * @param int $shareType
549
	 */
550
	public function userDeleted($uid, $shareType) {
551
		// TODO: Implement userDeleted() method.
552
	}
553
554
555
	/**
556
	 * A group is deleted from the system.
557
	 * We handle our own groups.
558
	 *
559
	 * @param string $gid
560
	 */
561
	public function groupDeleted($gid) {
562
		return;
563
	}
564
565
566
	/**
567
	 * A user is deleted from a group.
568
	 * We handle our own groups.
569
	 *
570
	 * @param string $uid
571
	 * @param string $gid
572
	 */
573
	public function userDeletedFromGroup($uid, $gid) {
574
		return;
575
	}
576
577
578
	/**
579
	 * Create a share object
580
	 *
581
	 * @param array $data
582
	 *
583
	 * @return IShare
584
	 */
585
	private function createShareObject($data) {
586
587
		$share = new Share($this->rootFolder, $this->userManager);
588
		$share->setId((int)$data['id'])
589
			  ->setPermissions((int)$data['permissions'])
590
			  ->setNodeType($data['item_type']);
591
592
		$share->setNodeId((int)$data['file_source'])
593
			  ->setTarget($data['file_target']);
594
595
		$this->assignShareObjectSharesProperties($share, $data);
596
		$this->assignShareObjectPropertiesFromParent($share, $data);
597
598
		$share->setProviderId($this->identifier());
599
600
		return $share;
601
	}
602
603
604
	/**
605
	 * @param IShare $share
606
	 * @param $data
607
	 */
608
	private function assignShareObjectPropertiesFromParent(IShare &$share, $data) {
609
		if (isset($data['f_permissions'])) {
610
			$entryData = $data;
611
			$entryData['permissions'] = $entryData['f_permissions'];
612
			$entryData['parent'] = $entryData['f_parent'];
613
			$share->setNodeCacheEntry(
614
				Cache::cacheEntryFromData(
615
					$entryData,
616
					\OC::$server->getMimeTypeLoader()
617
				)
618
			);
619
		}
620
	}
621
622
623
	/**
624
	 * @param IShare $share
625
	 * @param $data
626
	 */
627
	private function assignShareObjectSharesProperties(IShare &$share, $data) {
628
		$shareTime = new \DateTime();
629
		$shareTime->setTimestamp((int)$data['stime']);
630
631
		$share->setShareTime($shareTime);
632
		$share->setSharedWith($data['share_with'])
633
			  ->setSharedBy($data['uid_initiator'])
634
			  ->setShareOwner($data['uid_owner'])
635
			  ->setShareType((int)$data['share_type']);
636
637
		if (method_exists($share, 'setSharedWithDisplayName')) {
638
			$share->setSharedWithAvatar(CirclesService::getCircleIcon($data['circle_type']))
639
				  ->setSharedWithDisplayName(
640
					  sprintf(
641
						  '%s (%s, %s)', $data['circle_name'],
642
						  Circle::TypeLongString($data['circle_type']),
643
						  $this->miscService->getDisplayName($data['circle_owner'])
644
					  )
645
				  );
646
		}
647
	}
648
649
650
	/**
651
	 * @param IShare $share
652
	 *
653
	 * @return \Exception
654
	 */
655
	private function errorShareAlreadyExist($share) {
656
		$share_src = $share->getNode()
657
						   ->getName();
658
659
		$message = 'Sharing %s failed, this item is already shared with this circle';
660
		$message_t = $this->l10n->t($message, array($share_src));
661
		$this->logger->debug(
662
			sprintf($message, $share_src, $share->getSharedWith()), ['app' => 'circles']
663
		);
664
665
		return new \Exception($message_t);
666
	}
667
668
669
	/**
670
	 * Get the access list to the array of provided nodes.
671
	 *
672
	 * @see IManager::getAccessList() for sample docs
673
	 *
674
	 * @param Node[] $nodes The list of nodes to get access for
675
	 * @param bool $currentAccess If current access is required (like for removed shares that might
676
	 *     get revived later)
677
	 *
678
	 * @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...
679
	 * @since 12
680
	 */
681
	public function getAccessList($nodes, $currentAccess) {
682
		$ids = [];
683
		foreach ($nodes as $node) {
684
			$ids[] = $node->getId();
685
		}
686
687
		$qb = $this->getAccessListBaseSelectSql();
688
		$this->limitToFiles($qb, $ids);
689
690
		$users = $this->parseAccessListResult($qb);
691
692
		if ($currentAccess === false) {
693
			$users = array_keys($users);
694
		}
695
696
		return ['users' => $users];
697
	}
698
699
700
	/**
701
	 * return array regarding getAccessList format.
702
	 * ie. \OC\Share20\Manager::getAccessList()
703
	 *
704
	 * @param IQueryBuilder $qb
705
	 *
706
	 * @return array
707
	 */
708
	private function parseAccessListResult(IQueryBuilder $qb) {
709
710
		$cursor = $qb->execute();
711
		$users = [];
712
713
		while ($row = $cursor->fetch()) {
714
			$userId = $row['user_id'];
715
716
			if (!key_exists($userId, $users)) {
717
				$users[$userId] = [
718
					'node_id'   => $row['file_source'],
719
					'node_path' => $row['file_target']
720
				];
721
			}
722
		}
723
		$cursor->closeCursor();
724
725
		return $users;
726
	}
727
728
729
	/**
730
	 * @param IShare $share
731
	 *
732
	 * @return array
733
	 */
734
	private function shareObjectToArray(IShare $share) {
735
		return [
736
			'sharedWith'  => $share->getSharedWith(),
737
			'sharedBy'    => $share->getSharedBy(),
738
			'nodeId'      => $share->getNodeId(),
739
			'shareOwner'  => $share->getShareOwner(),
740
			'permissions' => $share->getPermissions(),
741
			'token'       => $share->getToken(),
742
			'password'    => $share->getPassword()
743
		];
744
	}
745
}
746