Completed
Push — master ( 8537c8...e6fb17 )
by Maxence
03:43
created

assignShareObjectPropertiesFromParent()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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