Completed
Push — master ( 703448...70edb2 )
by Maxence
02:33
created

getSharedWithCircleMembers()   C

Complexity

Conditions 7
Paths 8

Size

Total Lines 34
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
dl 0
loc 34
rs 6.7272
c 2
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
 * @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 OC\Files\Cache\Cache;
32
use OC\Share20\Share;
33
use OCA\Circles\AppInfo\Application;
34
use OCA\Circles\Db\CircleProviderRequestBuilder;
35
use OCA\Circles\Db\CirclesRequest;
36
use OCA\Circles\Db\MembersRequest;
37
use OCA\Circles\Model\Circle;
38
use OCA\Circles\Model\Member;
39
use OCA\Circles\Service\CirclesService;
40
use OCA\Circles\Service\ConfigService;
41
use OCA\Circles\Service\MiscService;
42
use OCP\Files\Folder;
43
use OCP\Files\IRootFolder;
44
use OCP\Files\Node;
45
use OCP\IDBConnection;
46
use OCP\IL10N;
47
use OCP\ILogger;
48
use OCP\IURLGenerator;
49
use OCP\IUserManager;
50
use OCP\Security\ISecureRandom;
51
use OCP\Share\Exceptions\ShareNotFound;
52
use OCP\Share\IShare;
53
use OCP\Share\IShareProvider;
54
55
class ShareByCircleProvider extends CircleProviderRequestBuilder implements IShareProvider {
56
57
	/** @var IDBConnection */
58
	protected $dbConnection;
59
60
	/** @var ILogger */
61
	private $logger;
62
63
	/** @var ISecureRandom */
64
	private $secureRandom;
65
66
	/** @var IUserManager */
67
	private $userManager;
68
69
	/** @var IRootFolder */
70
	private $rootFolder;
71
72
	/** @var IL10N */
73
	private $l;
74
75
	/** @var IURLGenerator */
76
	private $urlGenerator;
77
78
	/** @var CirclesRequest */
79
	private $circlesRequest;
80
81
	/** @var MembersRequest */
82
	private $membersRequest;
83
84
	/** @var ConfigService */
85
	private $configService;
86
87
	/** @var MiscService */
88
	private $miscService;
89
90
91
	/**
92
	 * DefaultShareProvider constructor.
93
	 *
94
	 * @param IDBConnection $connection
95
	 * @param ISecureRandom $secureRandom
96
	 * @param IUserManager $userManager
97
	 * @param IRootFolder $rootFolder
98
	 * @param IL10N $l
99
	 * @param ILogger $logger
100
	 * @param IURLGenerator $urlGenerator
101
	 */
102
	public function __construct(
103
		IDBConnection $connection, ISecureRandom $secureRandom, IUserManager $userManager,
104
		IRootFolder $rootFolder, IL10N $l, ILogger $logger, IURLGenerator $urlGenerator
105
	) {
106
		$this->dbConnection = $connection;
107
		$this->secureRandom = $secureRandom;
108
		$this->userManager = $userManager;
109
		$this->rootFolder = $rootFolder;
110
		$this->l = $l;
111
		$this->logger = $logger;
112
		$this->urlGenerator = $urlGenerator;
113
114
		$app = new Application();
115
		$this->circlesRequest = $app->getContainer()
116
									->query('CirclesRequest');
117
		$this->membersRequest = $app->getContainer()
118
									->query('MembersRequest');
119
		$this->configService = $app->getContainer()
120
								   ->query('ConfigService');
121
		$this->miscService = $app->getContainer()
122
								 ->query('MiscService');
123
	}
124
125
126
	/**
127
	 * Return the identifier of this provider.
128
	 *
129
	 * @return string
130
	 */
131
	public function identifier() {
132
		return 'ocCircleShare';
133
	}
134
135
136
	/**
137
	 * Create a share if it does not exist already.
138
	 *
139
	 * @param IShare $share
140
	 *
141
	 * @return IShare The share object
142
	 * @throws \Exception
143
	 */
144
	public function create(IShare $share) {
145
		try {
146
			$nodeId = $share->getNode()
147
							->getId();
148
149
			$qb = $this->findShareParentSql($nodeId, $share->getSharedWith());
150
			$exists = $qb->execute();
151
			$data = $exists->fetch();
152
			$exists->closeCursor();
153
154
			if ($data !== false) {
155
				throw $this->errorShareAlreadyExist($share);
156
			}
157
158
			$circle =
159
				$this->circlesRequest->getCircle($share->getSharedWith(), $share->getSharedby());
160
			$circle->getHigherViewer()
161
				   ->hasToBeMember();
162
163
			$shareId = $this->createShare($share);
164
165
			return $this->getShareById($shareId);
166
		} catch (\Exception $e) {
167
			throw $e;
168
		}
169
	}
170
171
172
	/**
173
	 * Update a share
174
	 * permissions right, owner and initiator
175
	 *
176
	 * @param IShare $share
177
	 *
178
	 * @return IShare The share object
179
	 */
180
	public function update(IShare $share) {
181
182
		$qb = $this->getBaseUpdateSql();
183
		$this->limitToShare($qb, $share->getId());
184
		$qb->set('permissions', $qb->createNamedParameter($share->getPermissions()))
185
		   ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
186
		   ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()));
187
		$qb->execute();
188
189
		return $share;
190
	}
191
192
193
	/**
194
	 * Delete a share, and it's children
195
	 *
196
	 * @param IShare $share
197
	 */
198
	public function delete(IShare $share) {
199
200
		$qb = $this->getBaseDeleteSql();
201
		$this->limitToShareAndChildren($qb, $share->getId());
202
203
		$qb->execute();
204
	}
205
206
207
	/**
208
	 * Unshare a file from self as recipient.
209
	 * Because every circles share are group shares, we will set permissions to 0
210
	 *
211
	 * @param IShare $share
212
	 * @param string $userId
213
	 */
214 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...
215
		$childId = $this->getShareChildId($share, $userId);
216
217
		$qb = $this->getBaseUpdateSql();
218
		$qb->set('permissions', $qb->createNamedParameter(0));
219
		$this->limitToShare($qb, $childId);
220
221
		$qb->execute();
222
	}
223
224
225
	/**
226
	 * Move a share as a recipient.
227
	 *
228
	 * @param IShare $share
229
	 * @param string $userId
230
	 *
231
	 * @return IShare
232
	 *
233
	 */
234 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...
235
236
		$childId = $this->getShareChildId($share, $userId);
237
238
		$qb = $this->getBaseUpdateSql();
239
		$qb->set('file_target', $qb->createNamedParameter($share->getTarget()));
240
		$this->limitToShare($qb, $childId);
241
		$qb->execute();
242
243
		return $share;
244
	}
245
246
247
	/**
248
	 * return the child ID of a share
249
	 *
250
	 * @param IShare $share
251
	 * @param string $userId
252
	 *
253
	 * @return bool
254
	 */
255
	private function getShareChildId(IShare $share, $userId) {
256
		$qb = $this->getBaseSelectSql($share->getId());
257
		$this->limitToShareChildren($qb, $userId, $share->getId());
258
259
		$child = $qb->execute();
260
		$data = $child->fetch();
261
		$child->closeCursor();
262
263
		if ($data === false) {
264
			return $this->createShareChild($userId, $share);
265
		}
266
267
		return $data['id'];
268
	}
269
270
271
	/**
272
	 * Create a child and returns its ID
273
	 *
274
	 * @param IShare $share
275
	 *
276
	 * @return int
277
	 */
278
	private function createShare($share) {
279
		$qb = $this->getBaseInsertSql($share);
280
		$qb->execute();
281
		$id = $qb->getLastInsertId();
282
283
		return (int)$id;
284
	}
285
286
287
	/**
288
	 * Create a child and returns its ID
289
	 *
290
	 * @param string $userId
291
	 * @param IShare $share
292
	 *
293
	 * @return int
294
	 */
295
	private function createShareChild($userId, $share) {
296
		$qb = $this->getBaseInsertSql($share);
297
298
		$qb->setValue('parent', $qb->createNamedParameter($share->getId()));
299
		$qb->setValue('share_with', $qb->createNamedParameter($userId));
300
		$qb->execute();
301
		$id = $qb->getLastInsertId();
302
303
		return (int)$id;
304
	}
305
306
307
	/**
308
	 * Get all shares by the given user in a folder
309
	 *
310
	 * @param string $userId
311
	 * @param Folder $node
312
	 * @param bool $reshares Also get the shares where $user is the owner instead of just the
313
	 *     shares where $user is the initiator
314
	 *
315
	 * @return Share[]
316
	 */
317
	public function getSharesInFolder($userId, Folder $node, $reshares) {
318
		$qb = $this->getBaseSelectSql();
319
		$this->limitToShareOwner($qb, $userId, true);
320
		$cursor = $qb->execute();
321
322
		$shares = [];
323
		while ($data = $cursor->fetch()) {
324
			$shares[$data['file_source']][] = $this->createShareObject($data);
325
		}
326
		$cursor->closeCursor();
327
328
		return $shares;
329
	}
330
331
332
	/**
333
	 * Get all shares by the given user
334
	 *
335
	 * @param string $userId
336
	 * @param int $shareType
337
	 * @param Node|null $node
338
	 * @param bool $reShares
339
	 * @param int $limit The maximum number of shares to be returned, -1 for all shares
340
	 * @param int $offset
341
	 *
342
	 * @return Share[]
343
	 */
344
	public function getSharesBy($userId, $shareType, $node, $reShares, $limit, $offset) {
345
		$qb = $this->getBaseSelectSql();
346
		$this->limitToShareOwner($qb, $userId, $reShares);
347
348
		if ($node !== null) {
349
			$this->limitToFiles($qb, $node->getId());
350
		}
351
352
		$this->limitToPage($qb, $limit, $offset);
353
		$cursor = $qb->execute();
354
355
		$shares = [];
356
		while ($data = $cursor->fetch()) {
357
			$shares[] = $this->createShareObject($this->editShareEntry($data));
358
		}
359
		$cursor->closeCursor();
360
361
		return $shares;
362
	}
363
364
365
	/**
366
	 * returns a better formatted string to display more information about
367
	 * the circle to the Sharing UI
368
	 *
369
	 * @param $data
370
	 *
371
	 * @return array<string,string>
372
	 */
373
	private function editShareEntry($data) {
374
		$data['share_with'] =
375
			sprintf(
376
				'%s (%s, %s)', $data['circle_name'], Circle::TypeLongString($data['circle_type']),
377
				\OC::$server->getUserManager()
378
							->get($data['circle_owner'])
379
							->getDisplayName()
380
			);
381
382
		return $data;
383
	}
384
385
386
	/**
387
	 * Get share by its id
388
	 *
389
	 * @param int $shareId
390
	 * @param string|null $recipientId
391
	 *
392
	 * @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...
393
	 * @throws ShareNotFound
394
	 */
395
	public function getShareById($shareId, $recipientId = null) {
396
		$qb = $this->getBaseSelectSql();
397
		$this->limitToShare($qb, $shareId);
398
399
		$cursor = $qb->execute();
400
		$data = $cursor->fetch();
401
		$cursor->closeCursor();
402
403
		if ($data === false) {
404
			throw new ShareNotFound();
405
		}
406
407
		return $this->createShareObject($data);
408
	}
409
410
411
	/**
412
	 * Get shares for a given path
413
	 *
414
	 * @param Node $path
415
	 *
416
	 * @return IShare[]|null
417
	 */
418
	public function getSharesByPath(Node $path) {
419
		return null;
420
	}
421
422
423
	/**
424
	 * Get shared with the given user
425
	 *
426
	 * @param string $userId get shares where this user is the recipient
427
	 * @param int $shareType
428
	 * @param Node|null $node
429
	 * @param int $limit The max number of entries returned, -1 for all
430
	 * @param int $offset
431
	 *
432
	 * @return array|IShare[]
433
	 */
434
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
435
436
		$shares = $this->getSharedWithCircleMembers($userId, $shareType, $node, $limit, $offset);
437
438
439
		return $shares;
440
	}
441
442
443
	private function getSharedWithCircleMembers($userId, $shareType, $node, $limit, $offset) {
444
445
		$qb = $this->getCompleteSelectSql();
446
		$this->linkToFileCache($qb, $userId);
447
		$this->limitToPage($qb, $limit, $offset);
448
449
		if ($node !== null) {
450
			$this->limitToFiles($qb, [$node->getId()]);
451
		}
452
453
		$this->linkToMember($qb, $userId, $this->configService->isLinkedGroupsAllowed());
454
455
		$this->leftJoinShareInitiator($qb);
456
		$cursor = $qb->execute();
457
458
		$shares = [];
459
		while ($data = $cursor->fetch()) {
460
461
			if ($data['initiator_circle_level'] < Member::LEVEL_MEMBER
462
				&& ($data['initiator_group_level'] < Member::LEVEL_MEMBER
463
					|| !$this->configService->isLinkedGroupsAllowed())
464
			) {
465
				continue;
466
			}
467
468
			self::editShareFromParentEntry($data);
469
			if (self::isAccessibleResult($data)) {
470
				$shares[] = $this->createShareObject($data);
471
			}
472
		}
473
		$cursor->closeCursor();
474
475
		return $shares;
476
	}
477
478
	/**
479
	 * @param $data
480
	 */
481
	private static function editShareFromParentEntry(&$data) {
482
		if ($data['parent_id'] > 0) {
483
			$data['permissions'] = $data['parent_perms'];
484
			$data['file_target'] = $data['parent_target'];
485
		}
486
	}
487
488
489
	/**
490
	 * Get a share by token
491
	 *
492
	 * @param string $token
493
	 *
494
	 * @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...
495
	 * @throws ShareNotFound
496
	 */
497
	public function getShareByToken($token) {
498
		return null;
499
	}
500
501
502
	/**
503
	 * We don't return a thing about children.
504
	 * The call to this function is deprecated and should be removed in next release of NC.
505
	 * Also, we get the children in the delete() method.
506
	 *
507
	 * @param IShare $parent
508
	 *
509
	 * @return array
510
	 */
511
	public function getChildren(IShare $parent) {
512
		return [];
513
	}
514
515
516
	/**
517
	 * A user is deleted from the system
518
	 * So clean up the relevant shares.
519
	 *
520
	 * @param string $uid
521
	 * @param int $shareType
522
	 */
523
	public function userDeleted($uid, $shareType) {
524
		// TODO: Implement userDeleted() method.
525
	}
526
527
528
	/**
529
	 * A group is deleted from the system.
530
	 * We handle our own groups.
531
	 *
532
	 * @param string $gid
533
	 */
534
	public function groupDeleted($gid) {
535
		return;
536
	}
537
538
539
	/**
540
	 * A user is deleted from a group.
541
	 * We handle our own groups.
542
	 *
543
	 * @param string $uid
544
	 * @param string $gid
545
	 */
546
	public function userDeletedFromGroup($uid, $gid) {
547
		return;
548
	}
549
550
551
	/**
552
	 * Create a share object
553
	 *
554
	 * @param array $data
555
	 *
556
	 * @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...
557
	 */
558
	private function createShareObject($data) {
559
560
		$share = new Share($this->rootFolder, $this->userManager);
561
		$share->setId((int)$data['id'])
562
			  ->setPermissions((int)$data['permissions'])
563
			  ->setNodeType($data['item_type']);
564
565
		$share->setNodeId((int)$data['file_source'])
566
			  ->setTarget($data['file_target']);
567
568
		$this->assignShareObjectSharesProperties($share, $data);
569
		$this->assignShareObjectPropertiesFromParent($share, $data);
570
571
		$share->setProviderId($this->identifier());
572
573
		return $share;
574
	}
575
576
577
	/**
578
	 * @param IShare $share
579
	 * @param $data
580
	 */
581
	private function assignShareObjectPropertiesFromParent(IShare &$share, $data) {
582
		if (isset($data['f_permissions'])) {
583
			$entryData = $data;
584
			$entryData['permissions'] = $entryData['f_permissions'];
585
			$entryData['parent'] = $entryData['f_parent'];
586
			$share->setNodeCacheEntry(
587
				Cache::cacheEntryFromData(
588
					$entryData,
589
					\OC::$server->getMimeTypeLoader()
590
				)
591
			);
592
		}
593
	}
594
595
596
	/**
597
	 * @param IShare $share
598
	 * @param $data
599
	 */
600
	private function assignShareObjectSharesProperties(IShare &$share, $data) {
601
		$shareTime = new \DateTime();
602
		$shareTime->setTimestamp((int)$data['stime']);
603
604
		$share->setShareTime($shareTime);
605
		$share->setSharedWith($data['share_with'])
606
			  ->setSharedBy($data['uid_initiator'])
607
			  ->setShareOwner($data['uid_owner'])
608
			  ->setShareType((int)$data['share_type']);
609
610
		if (method_exists($share, 'setSharedWithDisplayName')) {
611
			$share->setSharedWithAvatar(CirclesService::getCircleIcon($data['circle_type']))
612
				  ->setSharedWithDisplayName(
613
					  sprintf(
614
						  '%s (%s, %s)', $data['circle_name'],
615
						  Circle::TypeLongString($data['circle_type']),
616
						  \OC::$server->getUserManager()
617
									  ->get($data['circle_owner'])
618
									  ->getDisplayName()
619
					  )
620
				  );
621
		}
622
	}
623
624
625
	/**
626
	 * Returns whether the given database result can be interpreted as
627
	 * a share with accessible file (not trashed, not deleted)
628
	 *
629
	 * @param $data
630
	 *F
631
	 *
632
	 * @return bool
633
	 */
634
	private static function isAccessibleResult($data) {
635
		if ($data['fileid'] === null) {
636
			return false;
637
		}
638
639
		return (!(explode('/', $data['path'], 2)[0] !== 'files'
640
				  && explode(':', $data['storage_string_id'], 2)[0] === 'home'));
641
	}
642
643
644
	/**
645
	 * @param IShare $share
646
	 *
647
	 * @return \Exception
648
	 */
649
	private function errorShareAlreadyExist($share) {
650
		$share_src = $share->getNode()
651
						   ->getName();
652
653
		$message = 'Sharing %s failed, this item is already shared with this circle';
654
		$message_t = $this->l->t($message, array($share_src));
655
		$this->logger->debug(
656
			sprintf($message, $share_src, $share->getSharedWith()), ['app' => 'circles']
657
		);
658
659
		return new \Exception($message_t);
660
	}
661
662
663
	/**
664
	 * Get the access list to the array of provided nodes.
665
	 *
666
	 * @see IManager::getAccessList() for sample docs
667
	 *
668
	 * @param Node[] $nodes The list of nodes to get access for
669
	 * @param bool $currentAccess If current access is required (like for removed shares that might
670
	 *     get revived later)
671
	 *
672
	 * @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...
673
	 * @since 12
674
	 */
675
	public function getAccessList($nodes, $currentAccess) {
676
677
		$ids = [];
678
		foreach ($nodes as $node) {
679
			$ids[] = $node->getId();
680
		}
681
682
		$qb = $this->getAccessListBaseSelectSql();
683
		$this->limitToFiles($qb, $ids);
684
685
		$users = $this->parseAccessListResult($qb);
686
687
		if ($currentAccess === false) {
688
			$users = array_keys($users);
689
		}
690
691
		return ['users' => $users];
692
	}
693
694
695
	/**
696
	 * return array regarding getAccessList format.
697
	 * ie. \OC\Share20\Manager::getAccessList()
698
	 *
699
	 * @param $qb
700
	 *
701
	 * @return array
702
	 */
703
	private function parseAccessListResult($qb) {
704
705
		$cursor = $qb->execute();
706
		$users = [];
707
708
		while ($row = $cursor->fetch()) {
709
			$userId = $row['user_id'];
710
711
			if (!key_exists($userId, $users)) {
712
				$users[$userId] = [
713
					'node_id'   => $row['file_source'],
714
					'node_path' => $row['file_target']
715
				];
716
			}
717
		}
718
		$cursor->closeCursor();
719
720
		return $users;
721
	}
722
}
723