Completed
Push — master ( 7a8cc5...cbf9d7 )
by Maxence
02:26
created

ShareByCircleProvider::create()   B

Complexity

Conditions 3
Paths 11

Size

Total Lines 26
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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