Completed
Push — master ( 8932ec...0de15a )
by Joas
14:56
created

FederatedShareProvider::userDeleted()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 10
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 10
loc 10
ccs 0
cts 0
cp 0
rs 9.4285
cc 1
eloc 6
nc 1
nop 2
crap 2
1
<?php
2
/**
3
 * @author Björn Schießle <[email protected]>
4
 * @author Roeland Jago Douma <[email protected]>
5
 * @author Thomas Müller <[email protected]>
6
 *
7
 * @copyright Copyright (c) 2016, ownCloud, Inc.
8
 * @license AGPL-3.0
9
 *
10
 * This code is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Affero General Public License, version 3,
12
 * as published by the Free Software Foundation.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License, version 3,
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
21
 *
22
 */
23
24
namespace OCA\FederatedFileSharing;
25
26
use OC\Share20\Share;
27
use OCP\Files\IRootFolder;
28
use OCP\IL10N;
29
use OCP\ILogger;
30
use OCP\Share\IShare;
31
use OCP\Share\IShareProvider;
32
use OC\Share20\Exception\InvalidShare;
33
use OCP\Share\Exceptions\ShareNotFound;
34
use OCP\Files\NotFoundException;
35
use OCP\IDBConnection;
36
use OCP\Files\Node;
37
38
/**
39
 * Class FederatedShareProvider
40
 *
41
 * @package OCA\FederatedFileSharing
42
 */
43
class FederatedShareProvider implements IShareProvider {
44
45
	const SHARE_TYPE_REMOTE = 6;
46
47
	/** @var IDBConnection */
48
	private $dbConnection;
49
50
	/** @var AddressHandler */
51
	private $addressHandler;
52
53
	/** @var Notifications */
54
	private $notifications;
55
56
	/** @var TokenHandler */
57
	private $tokenHandler;
58
59
	/** @var IL10N */
60
	private $l;
61
62
	/** @var ILogger */
63
	private $logger;
64
65
	/** @var IRootFolder */
66
	private $rootFolder;
67
68
	/**
69
	 * DefaultShareProvider constructor.
70
	 *
71
	 * @param IDBConnection $connection
72
	 * @param AddressHandler $addressHandler
73
	 * @param Notifications $notifications
74
	 * @param TokenHandler $tokenHandler
75
	 * @param IL10N $l10n
76
	 * @param ILogger $logger
77 10
	 * @param IRootFolder $rootFolder
78
	 */
79
	public function __construct(
80
			IDBConnection $connection,
81
			AddressHandler $addressHandler,
82
			Notifications $notifications,
83
			TokenHandler $tokenHandler,
84
			IL10N $l10n,
85
			ILogger $logger,
86 10
			IRootFolder $rootFolder
87 10
	) {
88 10
		$this->dbConnection = $connection;
89 10
		$this->addressHandler = $addressHandler;
90 10
		$this->notifications = $notifications;
91 10
		$this->tokenHandler = $tokenHandler;
92 10
		$this->l = $l10n;
93 10
		$this->logger = $logger;
94
		$this->rootFolder = $rootFolder;
95
	}
96
97
	/**
98
	 * Return the identifier of this provider.
99
	 *
100 8
	 * @return string Containing only [a-zA-Z0-9]
101 8
	 */
102
	public function identifier() {
103
		return 'ocFederatedSharing';
104
	}
105
106
	/**
107
	 * Share a path
108
	 *
109
	 * @param IShare $share
110
	 * @return IShare The share object
111
	 * @throws ShareNotFound
112 9
	 * @throws \Exception
113
	 */
114 9
	public function create(IShare $share) {
115 9
116 9
		$shareWith = $share->getSharedWith();
117 9
		$itemSource = $share->getNodeId();
118 9
		$itemType = $share->getNodeType();
119 9
		$uidOwner = $share->getShareOwner();
120
		$permissions = $share->getPermissions();
121
		$sharedBy = $share->getSharedBy();
122
123
		/*
124 9
		 * Check if file is not already shared with the remote user
125 9
		 */
126 1
		$alreadyShared = $this->getSharedWith($shareWith, self::SHARE_TYPE_REMOTE, $share->getNode(), 1, 0);
127 1
		if (!empty($alreadyShared)) {
128 1
			$message = 'Sharing %s failed, because this item is already shared with %s';
129 1
			$message_t = $this->l->t('Sharing %s failed, because this item is already shared with %s', array($share->getNode()->getName(), $shareWith));
130
			$this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
131
			throw new \Exception($message_t);
132
		}
133
134 9
135 9
		// don't allow federated shares if source and target server are the same
136 9
		list($user, $remote) = $this->addressHandler->splitUserRemote($shareWith);
137 9
		$currentServer = $this->addressHandler->generateRemoteURL();
138 1
		$currentUser = $sharedBy;
139 1
		if ($this->addressHandler->compareAddresses($user, $remote, $currentUser, $currentServer)) {
140 1
			$message = 'Not allowed to create a federated share with the same user.';
141 1
			$message_t = $this->l->t('Not allowed to create a federated share with the same user');
142
			$this->logger->debug($message, ['app' => 'Federated File Sharing']);
143
			throw new \Exception($message_t);
144 8
		}
145
146 8
		$token = $this->tokenHandler->generateToken();
147
148 8
		$shareWith = $user . '@' . $remote;
149
150 8
		$shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
151 8
152 8
		$send = $this->notifications->sendRemoteShare(
153 8
			$token,
154 8
			$shareWith,
155 8
			$share->getNode()->getName(),
156 8
			$shareId,
157
			$share->getSharedBy()
158 8
		);
159 8
160
		$data = $this->getRawShare($shareId);
161 8
		$share = $this->createShare($data);
162 1
163 1
		if ($send === false) {
164 1
			$this->delete($share);
165 1
			$message_t = $this->l->t('Sharing %s failed, could not find %s, maybe the server is currently unreachable.',
166
				[$share->getNode()->getName(), $shareWith]);
167
			throw new \Exception($message_t);
168 7
		}
169
170
		return $share;
171
	}
172
173
	/**
174
	 * add share to the database and return the ID
175
	 *
176
	 * @param int $itemSource
177
	 * @param string $itemType
178
	 * @param string $shareWith
179
	 * @param string $sharedBy
180
	 * @param string $uidOwner
181
	 * @param int $permissions
182
	 * @param string $token
183 8
	 * @return int
184 8
	 */
185 8
	private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token) {
186 8
		$qb = $this->dbConnection->getQueryBuilder();
187 8
		$qb->insert('share')
188 8
			->setValue('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE))
189 8
			->setValue('item_type', $qb->createNamedParameter($itemType))
190 8
			->setValue('item_source', $qb->createNamedParameter($itemSource))
191 8
			->setValue('file_source', $qb->createNamedParameter($itemSource))
192 8
			->setValue('share_with', $qb->createNamedParameter($shareWith))
193 8
			->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
194 8
			->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
195 8
			->setValue('permissions', $qb->createNamedParameter($permissions))
196
			->setValue('token', $qb->createNamedParameter($token))
197
			->setValue('stime', $qb->createNamedParameter(time()));
198
199
		/*
200
		 * Added to fix https://github.com/owncloud/core/issues/22215
201 8
		 * Can be removed once we get rid of ajax/share.php
202
		 */
203 8
		$qb->setValue('file_target', $qb->createNamedParameter(''));
204 8
205
		$qb->execute();
206 8
		$id = $qb->getLastInsertId();
207
208
		return (int)$id;
209
	}
210
211
	/**
212
	 * Update a share
213
	 *
214
	 * @param IShare $share
215 1
	 * @return IShare The share object
216
	 */
217 View Code Duplication
	public function update(IShare $share) {
218
		/*
219 1
		 * We allow updating the permissions of federated shares
220 1
		 */
221 1
		$qb = $this->dbConnection->getQueryBuilder();
222 1
			$qb->update('share')
223 1
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
224 1
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
225 1
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
226
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
227 1
				->execute();
228
229
		return $share;
230
	}
231
232
	/**
233
	 * @inheritdoc
234
	 */
235
	public function move(IShare $share, $recipient) {
236
		/*
237
		 * This function does nothing yet as it is just for outgoing
238
		 * federated shares.
239
		 */
240
		return $share;
241
	}
242
243
	/**
244
	 * Get all children of this share
245
	 *
246
	 * @param IShare $parent
247
	 * @return IShare[]
248
	 */
249 View Code Duplication
	public function getChildren(IShare $parent) {
250
		$children = [];
251
252
		$qb = $this->dbConnection->getQueryBuilder();
253
		$qb->select('*')
254
			->from('share')
255
			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
256
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
257
			->orderBy('id');
258
259
		$cursor = $qb->execute();
260
		while($data = $cursor->fetch()) {
261
			$children[] = $this->createShare($data);
262
		}
263
		$cursor->closeCursor();
264
265
		return $children;
266
	}
267
268
	/**
269
	 * Delete a share
270
	 *
271 1
	 * @param IShare $share
272 1
	 */
273 1
	public function delete(IShare $share) {
274 1
		$qb = $this->dbConnection->getQueryBuilder();
275 1
		$qb->delete('share')
276
			->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())));
277 1
		$qb->execute();
278 1
279 1
		list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedWith());
280
		$this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken());
281
	}
282
283
	/**
284
	 * @inheritdoc
285
	 */
286
	public function deleteFromSelf(IShare $share, $recipient) {
287
		// nothing to do here. Technically deleteFromSelf in the context of federated
288
		// shares is a umount of a external storage. This is handled here
289
		// apps/files_sharing/lib/external/manager.php
290
		// TODO move this code over to this app
291
		return;
292
	}
293
294
	/**
295 6
	 * @inheritdoc
296 6
	 */
297 6
	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
298 6
		$qb = $this->dbConnection->getQueryBuilder();
299
		$qb->select('*')
300 6
			->from('share');
301
302
		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
303
304
		/**
305 6
		 * Reshares for this user are shares where they are the owner.
306
		 */
307 2
		if ($reshares === false) {
308 2
			//Special case for old shares created via the web UI
309 2
			$or1 = $qb->expr()->andX(
310 2
				$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
311
				$qb->expr()->isNull('uid_initiator')
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::andX() has too many arguments starting with $qb->expr()->isNull('uid_initiator').

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
312 2
			);
313 2
314 2
			$qb->andWhere(
315
				$qb->expr()->orX(
316 2
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
317 2
					$or1
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $or1.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
318 2
				)
319 4
			);
320 4
		} else {
321 4
			$qb->andWhere(
322 4
				$qb->expr()->orX(
323 4
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
324 4
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
0 ignored issues
show
Unused Code introduced by
The call to IExpressionBuilder::orX() has too many arguments starting with $qb->expr()->eq('uid_ini...amedParameter($userId)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
325
				)
326
			);
327 6
		}
328 1
329 1
		if ($node !== null) {
330
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
331 6
		}
332 1
333 1
		if ($limit !== -1) {
334
			$qb->setMaxResults($limit);
335 6
		}
336 6
337
		$qb->setFirstResult($offset);
338 6
		$qb->orderBy('id');
339 6
340 6
		$cursor = $qb->execute();
341 4
		$shares = [];
342 4
		while($data = $cursor->fetch()) {
343 6
			$shares[] = $this->createShare($data);
344
		}
345 6
		$cursor->closeCursor();
346
347
		return $shares;
348
	}
349
350
	/**
351 1
	 * @inheritdoc
352 1
	 */
353 View Code Duplication
	public function getShareById($id, $recipientId = null) {
354 1
		$qb = $this->dbConnection->getQueryBuilder();
355 1
356 1
		$qb->select('*')
357 1
			->from('share')
358
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
359 1
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
360 1
		
361 1
		$cursor = $qb->execute();
362
		$data = $cursor->fetch();
363 1
		$cursor->closeCursor();
364
365
		if ($data === false) {
366
			throw new ShareNotFound();
367
		}
368 1
369 1
		try {
370
			$share = $this->createShare($data);
371
		} catch (InvalidShare $e) {
372
			throw new ShareNotFound();
373 1
		}
374
375
		return $share;
376
	}
377
378
	/**
379
	 * Get shares for a given path
380
	 *
381
	 * @param \OCP\Files\Node $path
382
	 * @return IShare[]
383
	 */
384 View Code Duplication
	public function getSharesByPath(Node $path) {
385
		$qb = $this->dbConnection->getQueryBuilder();
386
387
		$cursor = $qb->select('*')
388
			->from('share')
389
			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
390
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
391
			->execute();
392
393
		$shares = [];
394
		while($data = $cursor->fetch()) {
395
			$shares[] = $this->createShare($data);
396
		}
397
		$cursor->closeCursor();
398
399
		return $shares;
400
	}
401
402
	/**
403 9
	 * @inheritdoc
404
	 */
405 9
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
406
		/** @var IShare[] $shares */
407
		$shares = [];
408 9
409 9
		//Get shares directly with this user
410 9
		$qb = $this->dbConnection->getQueryBuilder();
411
		$qb->select('*')
412
			->from('share');
413 9
414
		// Order by id
415
		$qb->orderBy('id');
416 9
417 9
		// Set limit and offset
418 9
		if ($limit !== -1) {
419 9
			$qb->setMaxResults($limit);
420
		}
421 9
		$qb->setFirstResult($offset);
422 9
423
		$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
424
		$qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
425 9
426 9
		// Filter by node if provided
427 9
		if ($node !== null) {
428
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
429 9
		}
430
431 9
		$cursor = $qb->execute();
432 1
433 1
		while($data = $cursor->fetch()) {
434 9
			$shares[] = $this->createShare($data);
435
		}
436
		$cursor->closeCursor();
437 9
438
439
		return $shares;
440
	}
441
442
	/**
443
	 * Get a share by token
444
	 *
445
	 * @param string $token
446
	 * @return IShare
447
	 * @throws ShareNotFound
448
	 */
449 View Code Duplication
	public function getShareByToken($token) {
450
		$qb = $this->dbConnection->getQueryBuilder();
451
452
		$cursor = $qb->select('*')
453
			->from('share')
454
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
455
			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
456
			->execute();
457
458
		$data = $cursor->fetch();
459
460
		if ($data === false) {
461
			throw new ShareNotFound();
462
		}
463
464
		try {
465
			$share = $this->createShare($data);
466
		} catch (InvalidShare $e) {
467
			throw new ShareNotFound();
468
		}
469
470
		return $share;
471
	}
472
473
	/**
474
	 * get database row of a give share
475
	 *
476
	 * @param $id
477
	 * @return array
478 8
	 * @throws ShareNotFound
479
	 */
480 View Code Duplication
	private function getRawShare($id) {
481 8
482 8
		// Now fetch the inserted share and create a complete share object
483 8
		$qb = $this->dbConnection->getQueryBuilder();
484 8
		$qb->select('*')
485
			->from('share')
486 8
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
487 8
488 8
		$cursor = $qb->execute();
489
		$data = $cursor->fetch();
490 8
		$cursor->closeCursor();
491
492
		if ($data === false) {
493
			throw new ShareNotFound;
494 8
		}
495
496
		return $data;
497
	}
498
499
	/**
500
	 * Create a share object from an database row
501
	 *
502
	 * @param array $data
503
	 * @return IShare
504
	 * @throws InvalidShare
505 8
	 * @throws ShareNotFound
506
	 */
507 8
	private function createShare($data) {
508 8
509 8
		$share = new Share($this->rootFolder);
510 8
		$share->setId((int)$data['id'])
511 8
			->setShareType((int)$data['share_type'])
512 8
			->setPermissions((int)$data['permissions'])
513 8
			->setTarget($data['file_target'])
514
			->setMailSend((bool)$data['mail_send'])
515 8
			->setToken($data['token']);
516 8
517 8
		$shareTime = new \DateTime();
518 8
		$shareTime->setTimestamp((int)$data['stime']);
519
		$share->setShareTime($shareTime);
520 8
		$share->setSharedWith($data['share_with']);
521 8
522 8
		if ($data['uid_initiator'] !== null) {
523 8
			$share->setShareOwner($data['uid_owner']);
524
			$share->setSharedBy($data['uid_initiator']);
525
		} else {
526
			//OLD SHARE
527
			$share->setSharedBy($data['uid_owner']);
528
			$path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
529
530
			$owner = $path->getOwner();
531
			$share->setShareOwner($owner->getUID());
532 8
		}
533 8
534
		$share->setNodeId((int)$data['file_source']);
535 8
		$share->setNodeType($data['item_type']);
536
537 8
		$share->setProviderId($this->identifier());
538
539
		return $share;
540
	}
541
542
	/**
543
	 * Get the node with file $id for $user
544
	 *
545
	 * @param string $userId
546
	 * @param int $id
547
	 * @return \OCP\Files\File|\OCP\Files\Folder
548
	 * @throws InvalidShare
549
	 */
550
	private function getNode($userId, $id) {
551
		try {
552
			$userFolder = $this->rootFolder->getUserFolder($userId);
553
		} catch (NotFoundException $e) {
554
			throw new InvalidShare();
555
		}
556
557
		$nodes = $userFolder->getById($id);
558
559
		if (empty($nodes)) {
560
			throw new InvalidShare();
561
		}
562
563
		return $nodes[0];
564
	}
565
566
	/**
567
	 * A user is deleted from the system
568
	 * So clean up the relevant shares.
569
	 *
570
	 * @param string $uid
571
	 * @param int $shareType
572
	 */
573 View Code Duplication
	public function userDeleted($uid, $shareType) {
574
		//TODO: probabaly a good idea to send unshare info to remote servers
575
576
		$qb = $this->dbConnection->getQueryBuilder();
577
578
		$qb->delete('share')
579
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE)))
580
			->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
581
			->execute();
582
	}
583
}
584