Completed
Push — master ( 2235ba...fa8fa8 )
by Maxence
02:29
created

ShareByCircleProvider::deleteFromSelf()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 9
Ratio 100 %

Importance

Changes 0
Metric Value
dl 9
loc 9
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 2
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
 * @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
28
namespace OCA\Circles;
29
30
31
use OCA\Circles\AppInfo\Application;
32
use OCA\Circles\Db\CircleProviderRequestBuilder;
33
use OCA\Circles\Db\CirclesRequest;
34
use OCA\Circles\Db\MembersRequest;
35
use OCA\Circles\Model\Circle;
36
use OCA\Circles\Model\Member;
37
use OCA\Circles\Service\CirclesService;
38
use OCA\Circles\Service\MiscService;
39
use OCP\DB\QueryBuilder\IQueryBuilder;
40
use OCP\Files\Folder;
41
use OCP\Files\Node;
42
use OCP\Files\IRootFolder;
43
use OC\Files\Cache\Cache;
44
use OC\Share20\Share;
45
use OCP\Share\IShare;
46
use OCP\Share\Exceptions\ShareNotFound;
47
use OCP\Share\IShareProvider;
48
use OCP\IDBConnection;
49
use OCP\IL10N;
50
use OCP\ILogger;
51
use OCP\IURLGenerator;
52
use OCP\Security\ISecureRandom;
53
use OCP\IUserManager;
54
55
class ShareByCircleProvider extends CircleProviderRequestBuilder implements IShareProvider {
56
57
	/** @var ILogger */
58
	private $logger;
59
60
	/** @var ISecureRandom */
61
	private $secureRandom;
62
63
	/** @var IUserManager */
64
	private $userManager;
65
66
	/** @var IRootFolder */
67
	private $rootFolder;
68
69
	/** @var IL10N */
70
	private $l;
71
72
	/** @var IURLGenerator */
73
	private $urlGenerator;
74
75
	/** @var CirclesRequest */
76
	private $circlesRequest;
77
78
	/** @var MembersRequest */
79
	private $membersRequest;
80
81
	/** @var MiscService */
82
	private $miscService;
83
84
85
	/**
86
	 * DefaultShareProvider constructor.
87
	 *
88
	 * @param IDBConnection $connection
89
	 * @param ISecureRandom $secureRandom
90
	 * @param IUserManager $userManager
91
	 * @param IRootFolder $rootFolder
92
	 * @param IL10N $l
93
	 * @param ILogger $logger
94
	 * @param IURLGenerator $urlGenerator
95
	 */
96
	public function __construct(
97
		IDBConnection $connection, ISecureRandom $secureRandom, IUserManager $userManager,
98
		IRootFolder $rootFolder, IL10N $l, ILogger $logger, IURLGenerator $urlGenerator
99
	) {
100
		$this->dbConnection = $connection;
101
		$this->secureRandom = $secureRandom;
102
		$this->userManager = $userManager;
103
		$this->rootFolder = $rootFolder;
104
		$this->l = $l;
105
		$this->logger = $logger;
106
		$this->urlGenerator = $urlGenerator;
107
108
		$app = new Application();
109
		$this->circlesRequest = $app->getContainer()
110
									->query('CirclesRequest');
111
		$this->membersRequest = $app->getContainer()
112
									->query('MembersRequest');
113
		$this->miscService = $app->getContainer()
114
								 ->query('MiscService');
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
			$circle =
151
				$this->circlesRequest->getCircle($share->getSharedWith(), $share->getSharedby());
152
			$circle->getHigherViewer()
153
				   ->hasToBeMember();
154
155
			$shareId = $this->createShare($share);
156
157
			return $this->getShareById($shareId);
158
		} catch (\Exception $e) {
159
			throw $e;
160
		}
161
	}
162
163
164
	/**
165
	 * Update a share
166
	 * permissions right, owner and initiator
167
	 *
168
	 * @param IShare $share
169
	 *
170
	 * @return IShare The share object
171
	 */
172
	public function update(IShare $share) {
173
174
		$qb = $this->getBaseUpdateSql();
175
		$this->limitToShare($qb, $share->getId());
176
		$qb->set('permissions', $qb->createNamedParameter($share->getPermissions()))
177
		   ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
178
		   ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()));
179
		$qb->execute();
180
181
		return $share;
182
	}
183
184
185
	/**
186
	 * Delete a share, and it's children
187
	 *
188
	 * @param IShare $share
189
	 */
190
	public function delete(IShare $share) {
191
192
		$qb = $this->getBaseDeleteSql();
193
		$this->limitToShareAndChildren($qb, $share->getId());
194
195
		$qb->execute();
196
	}
197
198
199
	/**
200
	 * Unshare a file from self as recipient.
201
	 * Because every circles share are group shares, we will set permissions to 0
202
	 *
203
	 * @param IShare $share
204
	 * @param string $userId
205
	 */
206 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...
207
		$childId = $this->getShareChildId($share, $userId);
208
209
		$qb = $this->getBaseUpdateSql();
210
		$qb->set('permissions', $qb->createNamedParameter(0));
211
		$this->limitToShare($qb, $childId);
212
213
		$qb->execute();
214
	}
215
216
217
	/**
218
	 * Move a share as a recipient.
219
	 *
220
	 * @param IShare $share
221
	 * @param string $userId
222
	 *
223
	 * @return IShare
224
	 *
225
	 */
226 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...
227
228
		$childId = $this->getShareChildId($share, $userId);
229
230
		$qb = $this->getBaseUpdateSql();
231
		$qb->set('file_target', $qb->createNamedParameter($share->getTarget()));
232
		$this->limitToShare($qb, $childId);
233
		$qb->execute();
234
235
		return $share;
236
	}
237
238
239
	/**
240
	 * return the child ID of a share
241
	 *
242
	 * @param IShare $share
243
	 * @param string $userId
244
	 *
245
	 * @return bool
246
	 */
247
	private function getShareChildId(IShare $share, $userId) {
248
		$qb = $this->getBaseSelectSql($share->getId());
249
		$this->limitToShareChildren($qb, $userId, $share->getId());
250
251
		$child = $qb->execute();
252
		$data = $child->fetch();
253
		$child->closeCursor();
254
255
		if ($data === false) {
256
			return $this->createShareChild($userId, $share);
257
		}
258
259
		return $data['id'];
260
	}
261
262
263
	/**
264
	 * Create a child and returns its ID
265
	 *
266
	 * @param IShare $share
267
	 *
268
	 * @return int
269
	 */
270
	private function createShare($share) {
271
		$qb = $this->getBaseInsertSql($share);
272
		$qb->execute();
273
		$id = $qb->getLastInsertId();
274
275
		return (int)$id;
276
	}
277
278
279
	/**
280
	 * Create a child and returns its ID
281
	 *
282
	 * @param string $userId
283
	 * @param IShare $share
284
	 *
285
	 * @return int
286
	 */
287
	private function createShareChild($userId, $share) {
288
		$qb = $this->getBaseInsertSql($share);
289
290
		$qb->setValue('parent', $qb->createNamedParameter($share->getId()));
291
		$qb->setValue('share_with', $qb->createNamedParameter($userId));
292
		$qb->execute();
293
		$id = $qb->getLastInsertId();
294
295
		return (int)$id;
296
	}
297
298
299
	/**
300
	 * Get all shares by the given user in a folder
301
	 *
302
	 * @param string $userId
303
	 * @param Folder $node
304
	 * @param bool $reshares Also get the shares where $user is the owner instead of just the
305
	 *     shares where $user is the initiator
306
	 *
307
	 * @return Share[]
308
	 */
309
	public function getSharesInFolder($userId, Folder $node, $reshares) {
310
		$qb = $this->getBaseSelectSql();
311
		$this->limitToShareOwner($qb, $userId, true);
312
		$cursor = $qb->execute();
313
314
		$shares = [];
315
		while ($data = $cursor->fetch()) {
316
			$shares[$data['file_source']][] = $this->createShareObject($data);
317
		}
318
		$cursor->closeCursor();
319
320
		return $shares;
321
	}
322
323
324
	/**
325
	 * Get all shares by the given user
326
	 *
327
	 * @param string $userId
328
	 * @param int $shareType
329
	 * @param Node|null $node
330
	 * @param bool $reShares
331
	 * @param int $limit The maximum number of shares to be returned, -1 for all shares
332
	 * @param int $offset
333
	 *
334
	 * @return Share[]
335
	 */
336
	public function getSharesBy($userId, $shareType, $node, $reShares, $limit, $offset) {
337
		$qb = $this->getBaseSelectSql();
338
		$this->limitToShareOwner($qb, $userId, $reShares);
339
340
		if ($node !== null) {
341
			$this->limitToFiles($qb, $node->getId());
342
		}
343
344
		$this->limitToPage($qb, $limit, $offset);
345
		$cursor = $qb->execute();
346
347
		$shares = [];
348
		while ($data = $cursor->fetch()) {
349
			$shares[] = $this->createShareObject($this->editShareEntry($data));
350
		}
351
		$cursor->closeCursor();
352
353
		return $shares;
354
	}
355
356
357
	/**
358
	 * returns a better formatted string to display more information about
359
	 * the circle to the Sharing UI
360
	 *
361
	 * @param $data
362
	 *
363
	 * @return array<string,string>
364
	 */
365
	private function editShareEntry($data) {
366
		$data['share_with'] =
367
			sprintf(
368
				'%s (%s, %s)', $data['circle_name'], Circle::TypeLongString($data['circle_type']),
369
				\OC::$server->getUserManager()
370
							->get($data['circle_owner'])
371
							->getDisplayName()
372
			);
373
374
		return $data;
375
	}
376
377
378
	/**
379
	 * Get share by its id
380
	 *
381
	 * @param int $shareId
382
	 * @param string|null $recipientId
383
	 *
384
	 * @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...
385
	 * @throws ShareNotFound
386
	 */
387
	public function getShareById($shareId, $recipientId = null) {
388
		$qb = $this->getBaseSelectSql();
389
		$this->limitToShare($qb, $shareId);
390
391
		$cursor = $qb->execute();
392
		$data = $cursor->fetch();
393
		$cursor->closeCursor();
394
395
		if ($data === false) {
396
			throw new ShareNotFound();
397
		}
398
399
		return $this->createShareObject($data);
400
	}
401
402
403
	/**
404
	 * Get shares for a given path
405
	 *
406
	 * @param Node $path
407
	 *
408
	 * @return IShare[]|null
409
	 */
410
	public function getSharesByPath(Node $path) {
411
		return null;
412
	}
413
414
415
	/**
416
	 * Get shared with the given user
417
	 *
418
	 * @param string $userId get shares where this user is the recipient
419
	 * @param int $shareType
420
	 * @param Node|null $node
421
	 * @param int $limit The max number of entries returned, -1 for all
422
	 * @param int $offset
423
	 *
424
	 * @return array|IShare[]
425
	 */
426
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
427
428
		$shares = $this->getSharedWithCircleMembers($userId, $shareType, $node, $limit, $offset);
429
430
431
		return $shares;
432
	}
433
434
435
	private function getSharedWithCircleMembers($userId, $shareType, $node, $limit, $offset) {
436
437
		$qb = $this->getCompleteSelectSql();
438
		$this->linkToFileCache($qb, $userId);
439
		$this->limitToPage($qb, $limit, $offset);
440
441
		if ($node !== null) {
442
			$this->limitToFiles($qb, [$node->getId()]);
443
		}
444
445
		$this->linkToMember($qb, $userId);
446
447
		$this->leftJoinShareInitiator($qb);
448
		$cursor = $qb->execute();
449
450
		$shares = [];
451
		while ($data = $cursor->fetch()) {
452
			
453
			if ($data['initiator_circle_level'] < Member::LEVEL_MEMBER
454
				&& $data['initiator_group_level'] < Member::LEVEL_MEMBER
455
			) {
456
				continue;
457
			}
458
459
			self::editShareFromParentEntry($data);
460
			if (self::isAccessibleResult($data)) {
461
				$shares[] = $this->createShareObject($data);
462
			}
463
		}
464
		$cursor->closeCursor();
465
466
		return $shares;
467
	}
468
469
	/**
470
	 * @param $data
471
	 */
472
	private static function editShareFromParentEntry(&$data) {
473
		if ($data['parent_id'] > 0) {
474
			$data['permissions'] = $data['parent_perms'];
475
			$data['file_target'] = $data['parent_target'];
476
		}
477
	}
478
479
480
	/**
481
	 * Get a share by token
482
	 *
483
	 * @param string $token
484
	 *
485
	 * @return IShare
0 ignored issues
show
Documentation introduced by
Should the return type not be IShare|null?

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...
486
	 * @throws ShareNotFound
487
	 */
488
	public function getShareByToken($token) {
489
		return null;
490
	}
491
492
493
	/**
494
	 * We don't return a thing about children.
495
	 * The call to this function is deprecated and should be removed in next release of NC.
496
	 * Also, we get the children in the delete() method.
497
	 *
498
	 * @param IShare $parent
499
	 *
500
	 * @return array
501
	 */
502
	public function getChildren(IShare $parent) {
503
		return [];
504
	}
505
506
507
	/**
508
	 * A user is deleted from the system
509
	 * So clean up the relevant shares.
510
	 *
511
	 * @param string $uid
512
	 * @param int $shareType
513
	 */
514
	public function userDeleted($uid, $shareType) {
515
		// TODO: Implement userDeleted() method.
516
	}
517
518
519
	/**
520
	 * A group is deleted from the system.
521
	 * We handle our own groups.
522
	 *
523
	 * @param string $gid
524
	 */
525
	public function groupDeleted($gid) {
526
		return;
527
	}
528
529
530
	/**
531
	 * A user is deleted from a group.
532
	 * We handle our own groups.
533
	 *
534
	 * @param string $uid
535
	 * @param string $gid
536
	 */
537
	public function userDeletedFromGroup($uid, $gid) {
538
		return;
539
	}
540
541
542
	/**
543
	 * Create a share object
544
	 *
545
	 * @param array $data
546
	 *
547
	 * @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...
548
	 */
549
	private function createShareObject($data) {
550
551
		$share = new Share($this->rootFolder, $this->userManager);
552
		$share->setId((int)$data['id'])
553
			  ->setPermissions((int)$data['permissions'])
554
			  ->setNodeType($data['item_type']);
555
556
		$share->setNodeId((int)$data['file_source'])
557
			  ->setTarget($data['file_target']);
558
559
		$this->assignShareObjectSharesProperties($share, $data);
560
		$this->assignShareObjectPropertiesFromParent($share, $data);
561
562
		$share->setProviderId($this->identifier());
563
564
		return $share;
565
	}
566
567
568
	/**
569
	 * @param IShare $share
570
	 * @param $data
571
	 */
572
	private function assignShareObjectPropertiesFromParent(& $share, $data) {
573
		if (isset($data['f_permissions'])) {
574
			$entryData = $data;
575
			$entryData['permissions'] = $entryData['f_permissions'];
576
			$entryData['parent'] = $entryData['f_parent'];
577
			$share->setNodeCacheEntry(
578
				Cache::cacheEntryFromData(
579
					$entryData,
580
					\OC::$server->getMimeTypeLoader()
581
				)
582
			);
583
		}
584
	}
585
586
587
	/**
588
	 * @param IShare $share
589
	 * @param $data
590
	 */
591
	private function assignShareObjectSharesProperties(IShare &$share, $data) {
592
		$shareTime = new \DateTime();
593
		$shareTime->setTimestamp((int)$data['stime']);
594
595
		$share->setShareTime($shareTime);
596
		$share->setSharedWith($data['share_with'])
597
			  ->setSharedBy($data['uid_initiator'])
598
			  ->setShareOwner($data['uid_owner'])
599
			  ->setShareType((int)$data['share_type']);
600
601
		if (method_exists($share, 'setSharedWithDisplayName')) {
602
			$share->setSharedWithAvatar(CirclesService::getCircleIcon($data['circle_type']))
603
				  ->setSharedWithDisplayName(
604
					  sprintf(
605
						  '%s (%s, %s)', $data['circle_name'],
606
						  Circle::TypeLongString($data['circle_type']),
607
						  \OC::$server->getUserManager()
608
									  ->get($data['circle_owner'])
609
									  ->getDisplayName()
610
					  )
611
				  );
612
		}
613
	}
614
615
616
	/**
617
	 * Returns whether the given database result can be interpreted as
618
	 * a share with accessible file (not trashed, not deleted)
619
	 *
620
	 * @param $data
621
	 *F
622
	 *
623
	 * @return bool
624
	 */
625
	private static function isAccessibleResult($data) {
626
		if ($data['fileid'] === null) {
627
			return false;
628
		}
629
630
		return (!(explode('/', $data['path'], 2)[0] !== 'files'
631
				  && explode(':', $data['storage_string_id'], 2)[0] === 'home'));
632
	}
633
634
635
	/**
636
	 * @param IShare $share
637
	 *
638
	 * @return \Exception
639
	 */
640
	private function errorShareAlreadyExist($share) {
641
		$share_src = $share->getNode()
642
						   ->getName();
643
644
		$message = 'Sharing %s failed, this item is already shared with this circle';
645
		$message_t = $this->l->t($message, array($share_src));
646
		$this->logger->debug(
647
			sprintf($message, $share_src, $share->getSharedWith()), ['app' => 'circles']
648
		);
649
650
		return new \Exception($message_t);
651
	}
652
653
654
	/**
655
	 * Get the access list to the array of provided nodes.
656
	 *
657
	 * @see IManager::getAccessList() for sample docs
658
	 *
659
	 * @param Node[] $nodes The list of nodes to get access for
660
	 * @param bool $currentAccess If current access is required (like for removed shares that might
661
	 *     get revived later)
662
	 *
663
	 * @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...
664
	 * @since 12
665
	 */
666
	public function getAccessList($nodes, $currentAccess) {
667
668
		$ids = [];
669
		foreach ($nodes as $node) {
670
			$ids[] = $node->getId();
671
		}
672
673
		$qb = $this->getAccessListBaseSelectSql();
674
		$this->limitToFiles($qb, $ids);
675
676
		$users = $this->parseAccessListResult($qb);
677
678
		if ($currentAccess === false) {
679
			$users = array_keys($users);
680
		}
681
682
		return ['users' => $users];
683
	}
684
685
686
	/**
687
	 * return array regarding getAccessList format.
688
	 * ie. \OC\Share20\Manager::getAccessList()
689
	 *
690
	 * @param $qb
691
	 *
692
	 * @return array
693
	 */
694
	private function parseAccessListResult($qb) {
695
696
		$cursor = $qb->execute();
697
		$users = [];
698
699
		while ($row = $cursor->fetch()) {
700
			$userId = $row['user_id'];
701
702
			if (!key_exists($userId, $users)) {
703
				$users[$userId] = [
704
					'node_id'   => $row['file_source'],
705
					'node_path' => $row['file_target']
706
				];
707
			}
708
		}
709
		$cursor->closeCursor();
710
711
		return $users;
712
	}
713
}
714