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

ShareByCircleProvider::isAccessibleResult()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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