Completed
Pull Request — master (#62)
by Roeland
08:00
created

ShareByCircleProvider::getSharedWith()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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