Completed
Pull Request — master (#32303)
by Victor
09:20
created

FederatedShareProvider::createFederatedShare()   B

Complexity

Conditions 5
Paths 37

Size

Total Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 37
nop 1
dl 0
loc 51
rs 8.7579
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author Björn Schießle <[email protected]>
4
 * @author Robin Appelman <[email protected]>
5
 * @author Roeland Jago Douma <[email protected]>
6
 * @author Thomas Müller <[email protected]>
7
 *
8
 * @copyright Copyright (c) 2018, ownCloud GmbH
9
 * @license AGPL-3.0
10
 *
11
 * This code is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License, version 3,
13
 * as published by the Free Software Foundation.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License, version 3,
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
22
 *
23
 */
24
25
namespace OCA\FederatedFileSharing;
26
27
use OC\Share20\Exception\InvalidShare;
28
use OC\Share20\Share;
29
use OCP\DB\QueryBuilder\IQueryBuilder;
30
use OCP\Files\IRootFolder;
31
use OCP\Files\Node;
32
use OCP\Files\NotFoundException;
33
use OCP\IConfig;
34
use OCP\IDBConnection;
35
use OCP\IL10N;
36
use OCP\ILogger;
37
use OCP\IUserManager;
38
use OCP\Share\Exceptions\ShareNotFound;
39
use OCP\Share\IShare;
40
use OCP\Share\IShareProvider;
41
42
/**
43
 * Class FederatedShareProvider
44
 *
45
 * @package OCA\FederatedFileSharing
46
 */
47
class FederatedShareProvider implements IShareProvider {
48
	const SHARE_TYPE_REMOTE = 6;
49
50
	/** @var IDBConnection */
51
	private $dbConnection;
52
53
	/** @var AddressHandler */
54
	private $addressHandler;
55
56
	/** @var Notifications */
57
	private $notifications;
58
59
	/** @var TokenHandler */
60
	private $tokenHandler;
61
62
	/** @var IL10N */
63
	private $l;
64
65
	/** @var ILogger */
66
	private $logger;
67
68
	/** @var IRootFolder */
69
	private $rootFolder;
70
71
	/** @var IConfig */
72
	private $config;
73
74
	/** @var string */
75
	private $externalShareTable = 'share_external';
76
77 10
	/** @var string */
78
	private $shareTable = 'share';
79
80
	/** @var IUserManager */
81
	private $userManager;
82
83
	/**
84
	 * DefaultShareProvider constructor.
85
	 *
86 10
	 * @param IDBConnection $connection
87 10
	 * @param AddressHandler $addressHandler
88 10
	 * @param Notifications $notifications
89 10
	 * @param TokenHandler $tokenHandler
90 10
	 * @param IL10N $l10n
91 10
	 * @param ILogger $logger
92 10
	 * @param IRootFolder $rootFolder
93 10
	 * @param IConfig $config
94
	 * @param IUserManager $userManager
95
	 */
96 View Code Duplication
	public function __construct(
97
			IDBConnection $connection,
98
			AddressHandler $addressHandler,
99
			Notifications $notifications,
100 8
			TokenHandler $tokenHandler,
101 8
			IL10N $l10n,
102
			ILogger $logger,
103
			IRootFolder $rootFolder,
104
			IConfig $config,
105
			IUserManager $userManager
106
	) {
107
		$this->dbConnection = $connection;
108
		$this->addressHandler = $addressHandler;
109
		$this->notifications = $notifications;
110
		$this->tokenHandler = $tokenHandler;
111
		$this->l = $l10n;
112 9
		$this->logger = $logger;
113
		$this->rootFolder = $rootFolder;
114 9
		$this->config = $config;
115 9
		$this->userManager = $userManager;
116 9
	}
117 9
118 9
	/**
119 9
	 * Return the identifier of this provider.
120
	 *
121
	 * @return string Containing only [a-zA-Z0-9]
122
	 */
123
	public function identifier() {
124 9
		return 'ocFederatedSharing';
125 9
	}
126 1
127 1
	/**
128 1
	 * Share a path
129 1
	 *
130
	 * @param IShare $share
131
	 * @return IShare The share object
132
	 * @throws ShareNotFound
133
	 * @throws \Exception
134 9
	 */
135 9
	public function create(IShare $share) {
136 9
		$shareWith = $share->getSharedWith();
137 9
		$itemSource = $share->getNodeId();
138 1
		$itemType = $share->getNodeType();
139 1
		$permissions = $share->getPermissions();
140 1
		$sharedBy = $share->getSharedBy();
141 1
		
142
		/*
143
		 * Check if file is not already shared with the remote user
144 8
		 */
145
		$alreadyShared = $this->getSharedWith($shareWith, self::SHARE_TYPE_REMOTE, $share->getNode(), 1, 0);
146 8
		if (!empty($alreadyShared)) {
147
			$message = 'Sharing %s failed, because this item is already shared with %s';
148 8
			$message_t = $this->l->t('Sharing %s failed, because this item is already shared with %s', [$share->getNode()->getName(), $shareWith]);
149
			$this->logger->debug(\sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
150 8
			throw new \Exception($message_t);
151 8
		}
152 8
153 8
		// don't allow federated shares if source and target server are the same
154 8
		list($user, $remote) = $this->addressHandler->splitUserRemote($shareWith);
155 8
		$currentServer = $this->addressHandler->generateRemoteURL();
156 8
		$currentUser = $sharedBy;
157
		if ($this->addressHandler->compareAddresses($user, $remote, $currentUser, $currentServer)) {
158 8
			$message = 'Not allowed to create a federated share with the same user.';
159 8
			$message_t = $this->l->t('Not allowed to create a federated share with the same user');
160
			$this->logger->debug($message, ['app' => 'Federated File Sharing']);
161 8
			throw new \Exception($message_t);
162 1
		}
163 1
164 1
		$share->setSharedWith($user . '@' . $remote);
165 1
166
		try {
167
			$remoteShare = $this->getShareFromExternalShareTable($share);
168 7
		} catch (ShareNotFound $e) {
169
			$remoteShare = null;
170
		}
171
172
		if ($remoteShare) {
173
			try {
174
				$uidOwner = $remoteShare['owner'] . '@' . $remoteShare['remote'];
175
				$shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, 'tmp_token_' . \time());
176
				$share->setId($shareId);
177
				list($token, $remoteId) = $this->askOwnerToReShare($shareWith, $share, $shareId);
178
				// remote share was create successfully if we get a valid token as return
179
				$send = \is_string($token) && $token !== '';
180
			} catch (\Exception $e) {
181
				// fall back to old re-share behavior if the remote server
182
				// doesn't support flat re-shares (was introduced with ownCloud 9.1)
183 8
				$this->removeShareFromTable($share);
184 8
				$shareId = $this->createFederatedShare($share);
185 8
			}
186 8
			if ($send) {
0 ignored issues
show
Bug introduced by
The variable $send does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
187 8
				$this->updateSuccessfulReshare($shareId, $token);
0 ignored issues
show
Bug introduced by
The variable $token does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
188 8
				$this->storeRemoteId($shareId, $remoteId);
0 ignored issues
show
Bug introduced by
The variable $remoteId does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
189 8
			} else {
190 8
				$this->removeShareFromTable($share);
191 8
				$message_t = $this->l->t('File is already shared with %s', [$shareWith]);
192 8
				throw new \Exception($message_t);
193 8
			}
194 8
		} else {
195 8
			$shareId = $this->createFederatedShare($share);
196
		}
197
198
		$data = $this->getRawShare($shareId);
199
		return $this->createShareObject($data);
200
	}
201 8
202
	/**
203 8
	 * create federated share and inform the recipient
204 8
	 *
205
	 * @param IShare $share
206 8
	 * @return int
207
	 * @throws ShareNotFound
208
	 * @throws \Exception
209
	 */
210
	protected function createFederatedShare(IShare $share) {
211
		$token = $this->tokenHandler->generateToken();
212
		$shareId = $this->addShareToDB(
213
			$share->getNodeId(),
214
			$share->getNodeType(),
215 1
			$share->getSharedWith(),
216
			$share->getSharedBy(),
217
			$share->getShareOwner(),
218
			$share->getPermissions(),
219 1
			$token
220 1
		);
221 1
222 1
		try {
223 1
			$instanceCloudId = $this->addressHandler->generateRemoteURL();
224 1
			$sharedBy = $share->getSharedBy();
225 1
			$sharedByCloudId = '';
226
			if ($this->userManager->userExists($sharedBy)) {
227 1
				$sharedByCloudId = $instanceCloudId;
228
			}
229
230
			$sharedByFullAddress =  ($sharedByCloudId !== '')
231
				? "{$sharedBy}@{$sharedByCloudId}"
232
				: $sharedBy;
233
234
			$sharedByAddress = new Address($sharedByFullAddress);
235
			$owner = $share->getShareOwner();
236
			$ownerAddress = new Address("{$owner}@{$instanceCloudId}");
237
			$sharedWith = $share->getSharedWith();
238
			$shareWithAddress = new Address($sharedWith);
239
			$send = $this->notifications->sendRemoteShare(
240
				$shareWithAddress,
241
				$ownerAddress,
242
				$sharedByAddress,
243
				$token,
244
				$share->getNode()->getName(),
245
				$shareId
246
			);
247
248
			if ($send === false) {
249
				$message_t = $this->l->t('Sharing %s failed, could not find %s, maybe the server is currently unreachable.',
250
					[$share->getNode()->getName(), $share->getSharedWith()]);
251
				throw new \Exception($message_t);
252
			}
253
		} catch (\Exception $e) {
254
			$this->logger->error('Failed to notify remote server of federated share, removing share (' . $e->getMessage() . ')');
255
			$this->removeShareFromTableById($shareId);
256
			throw $e;
257
		}
258
259
		return $shareId;
260
	}
261
262
	/**
263
	 * @param string $shareWith
264
	 * @param IShare $share
265
	 * @param string $shareId internal share Id
266
	 * @return array
267
	 * @throws \Exception
268
	 */
269
	protected function askOwnerToReShare($shareWith, IShare $share, $shareId) {
270
		$remoteShare = $this->getShareFromExternalShareTable($share);
271 1
		$token = $remoteShare['share_token'];
272 1
		$remoteId = $remoteShare['remote_id'];
273 1
		$remote = $remoteShare['remote'];
274 1
275 1
		list($token, $remoteId) = $this->notifications->requestReShare(
276
			$token,
277 1
			$remoteId,
278 1
			$shareId,
279 1
			$remote,
280
			$shareWith,
281
			$share->getPermissions()
282
		);
283
284
		return [$token, $remoteId];
285
	}
286
287
	/**
288
	 * get federated share from the share_external table but exclude mounted link shares
289
	 *
290
	 * @param IShare $share
291
	 * @return array
292
	 * @throws ShareNotFound
293
	 */
294
	protected function getShareFromExternalShareTable(IShare $share) {
295 6
		$query = $this->dbConnection->getQueryBuilder();
296 6
		$query->select('*')->from($this->externalShareTable)
297 6
			->where($query->expr()->eq('user', $query->createNamedParameter($share->getShareOwner())))
298 6
			->andWhere($query->expr()->eq('mountpoint', $query->createNamedParameter($share->getTarget())));
299
		$result = $query->execute()->fetchAll();
300 6
301
		if (isset($result[0]) && (int)$result[0]['remote_id'] > 0) {
302
			return $result[0];
303
		}
304
305 6
		throw new ShareNotFound('share not found in share_external table');
306
	}
307 2
308 2
	/**
309 2
	 * add share to the database and return the ID
310 2
	 *
311
	 * @param int $itemSource
312 2
	 * @param string $itemType
313 2
	 * @param string $shareWith
314 2
	 * @param string $sharedBy
315
	 * @param string $uidOwner
316 2
	 * @param int $permissions
317 2
	 * @param string $token
318 2
	 * @return int
319 4
	 */
320 4
	private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token) {
321 4
		$qb = $this->dbConnection->getQueryBuilder();
322 4
		$qb->insert($this->shareTable)
323 4
			->setValue('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE))
324 4
			->setValue('item_type', $qb->createNamedParameter($itemType))
325
			->setValue('item_source', $qb->createNamedParameter($itemSource))
326
			->setValue('file_source', $qb->createNamedParameter($itemSource))
327 6
			->setValue('share_with', $qb->createNamedParameter($shareWith))
328 1
			->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
329 1
			->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
330
			->setValue('permissions', $qb->createNamedParameter($permissions))
331 6
			->setValue('token', $qb->createNamedParameter($token))
332 1
			->setValue('stime', $qb->createNamedParameter(\time()));
333 1
334
		/*
335 6
		 * Added to fix https://github.com/owncloud/core/issues/22215
336 6
		 * Can be removed once we get rid of ajax/share.php
337
		 */
338 6
		$qb->setValue('file_target', $qb->createNamedParameter(''));
339 6
340 6
		$qb->execute();
341 4
		$id = $qb->getLastInsertId();
342 4
343 6
		return (int)$id;
344
	}
345 6
346
	/**
347
	 * Update a share
348
	 *
349
	 * @param IShare $share
350
	 * @return IShare The share object
351 1
	 */
352 1
	public function update(IShare $share) {
353
		/*
354 1
		 * We allow updating the permissions of federated shares
355 1
		 */
356 1
		$qb = $this->dbConnection->getQueryBuilder();
357 1
		$qb->update($this->shareTable)
358
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
359 1
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
360 1
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
361 1
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
362
				->execute();
363 1
364
		// send the updated permission to the owner/initiator, if they are not the same
365
		if ($share->getShareOwner() !== $share->getSharedBy()) {
366
			$this->sendPermissionUpdate($share);
367
		}
368 1
369 1
		return $share;
370
	}
371
372
	/**
373 1
	 * send the updated permission to the owner/initiator, if they are not the same
374
	 *
375
	 * @param IShare $share
376
	 * @throws ShareNotFound
377
	 * @throws \OC\HintException
378
	 */
379 View Code Duplication
	protected function sendPermissionUpdate(IShare $share) {
380
		$remoteId = $this->getRemoteId($share);
381
		// if the local user is the owner we send the permission change to the initiator
382
		if ($this->userManager->userExists($share->getShareOwner())) {
383
			list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
384
		} else { // ... if not we send the permission change to the owner
385
			list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
386
		}
387
		$this->notifications->sendPermissionChange($remote, $remoteId, $share->getToken(), $share->getPermissions());
388
	}
389
390
	/**
391
	 * update successful reShare with the correct token
392
	 *
393
	 * @param int $shareId
394
	 * @param string $token
395
	 */
396 View Code Duplication
	protected function updateSuccessfulReShare($shareId, $token) {
397
		$query = $this->dbConnection->getQueryBuilder();
398
		$query->update($this->shareTable)
399
			->where($query->expr()->eq('id', $query->createNamedParameter($shareId)))
400
			->set('token', $query->createNamedParameter($token))
401
			->execute();
402
	}
403 9
404
	/**
405 9
	 * store remote ID in federated reShare table
406
	 *
407
	 * @param $shareId
408 9
	 * @param $remoteId
409 9
	 */
410 9
	public function storeRemoteId($shareId, $remoteId) {
411
		$query = $this->dbConnection->getQueryBuilder();
412
		$query->insert('federated_reshares')
413 9
			->values(
414
				[
415
					'share_id' =>  $query->createNamedParameter($shareId),
416 9
					'remote_id' => $query->createNamedParameter($remoteId),
417 9
				]
418 9
			);
419 9
		$query->execute();
420
	}
421 9
422 9
	/**
423
	 * get share ID on remote server for federated re-shares
424
	 *
425 9
	 * @param IShare $share
426 9
	 * @return int
427 9
	 * @throws ShareNotFound
428
	 */
429 9
	public function getRemoteId(IShare $share) {
430
		$query = $this->dbConnection->getQueryBuilder();
431 9
		$query->select('remote_id')->from('federated_reshares')
432 1
			->where($query->expr()->eq('share_id', $query->createNamedParameter((int)$share->getId())));
433 1
		$data = $query->execute()->fetch();
434 9
435
		if (!\is_array($data) || !isset($data['remote_id'])) {
436
			throw new ShareNotFound();
437 9
		}
438
439
		return (int)$data['remote_id'];
440
	}
441
442
	/**
443
	 * @inheritdoc
444
	 */
445
	public function move(IShare $share, $recipient) {
446
		/*
447
		 * This function does nothing yet as it is just for outgoing
448
		 * federated shares.
449
		 */
450
		return $share;
451
	}
452
453
	/**
454
	 * Get all children of this share
455
	 *
456
	 * @param IShare $parent
457
	 * @return IShare[]
458
	 */
459 View Code Duplication
	public function getChildren(IShare $parent) {
460
		$children = [];
461
462
		$qb = $this->dbConnection->getQueryBuilder();
463
		$qb->select('*')
464
			->from($this->shareTable)
465
			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
466
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
467
			->orderBy('id');
468
469
		$cursor = $qb->execute();
470
		while ($data = $cursor->fetch()) {
471
			$children[] = $this->createShareObject($data);
472
		}
473
		$cursor->closeCursor();
474
475
		return $children;
476
	}
477
478 8
	/**
479
	 * Delete a share (owner unShares the file)
480
	 *
481 8
	 * @param IShare $share
482 8
	 */
483 8
	public function delete(IShare $share) {
484 8
		list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedWith());
485
486 8
		$isOwner = false;
487 8
488 8
		$this->removeShareFromTable($share);
489
490 8
		// if the local user is the owner we can send the unShare request directly...
491
		if ($this->userManager->userExists($share->getShareOwner())) {
492
			$this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken());
493
			$this->revokeShare($share, true);
494 8
			$isOwner = true;
495
		} else { // ... if not we need to correct ID for the unShare request
496
			$remoteId = $this->getRemoteId($share);
497
			$this->notifications->sendRemoteUnShare($remote, $remoteId, $share->getToken());
498
			$this->revokeShare($share, false);
499
		}
500
501
		// send revoke notification to the other user, if initiator and owner are not the same user
502
		if ($share->getShareOwner() !== $share->getSharedBy()) {
503
			$remoteId = $this->getRemoteId($share);
504
			if ($isOwner) {
505 8
				list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
506
			} else {
507 8
				list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
508 8
			}
509 8
			$this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken());
510 8
		}
511 8
	}
512 8
513 8
	/**
514
	 * in case of a re-share we need to send the other use (initiator or owner)
515 8
	 * a message that the file was unshared
516 8
	 *
517 8
	 * @param IShare $share
518 8
	 * @param bool $isOwner the user can either be the owner or the user who re-sahred it
519
	 * @throws ShareNotFound
520 8
	 * @throws \OC\HintException
521 8
	 */
522 8 View Code Duplication
	protected function revokeShare($share, $isOwner) {
523 8
		// also send a unShare request to the initiator, if this is a different user than the owner
524
		if ($share->getShareOwner() !== $share->getSharedBy()) {
525
			if ($isOwner) {
526
				list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
527
			} else {
528
				list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
529
			}
530
			$remoteId = $this->getRemoteId($share);
531
			$this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken());
532 8
		}
533 8
	}
534
535 8
	/**
536
	 * remove share from table
537 8
	 *
538
	 * @param IShare $share
539
	 */
540
	public function removeShareFromTable(IShare $share) {
541
		$this->removeShareFromTableById($share->getId());
542
	}
543
544
	/**
545
	 * remove share from table
546
	 *
547
	 * @param string $shareId
548
	 */
549 View Code Duplication
	private function removeShareFromTableById($shareId) {
550
		$qb = $this->dbConnection->getQueryBuilder();
551
		$qb->delete($this->shareTable)
552
			->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)));
553
		$qb->execute();
554
555
		$qb->delete('federated_reshares')
556
			->where($qb->expr()->eq('share_id', $qb->createNamedParameter($shareId)));
557
		$qb->execute();
558
	}
559
560
	/**
561
	 * @inheritdoc
562
	 */
563
	public function deleteFromSelf(IShare $share, $recipient) {
564
		// nothing to do here. Technically deleteFromSelf in the context of federated
565
		// shares is a umount of a external storage. This is handled here
566
		// apps/files_sharing/lib/external/manager.php
567
		// TODO move this code over to this app
568
		return;
569
	}
570
571
	/**
572
	 * @inheritdoc
573
	 */
574
	public function getAllSharesBy($userId, $shareTypes, $nodeIDs, $reshares) {
575
		$shares = [];
576
		$qb = $this->dbConnection->getQueryBuilder();
577
578
		$nodeIdsChunks = \array_chunk($nodeIDs, 100);
579
		foreach ($nodeIdsChunks as $nodeIdsChunk) {
580
			// In federates sharing currently we have only one share_type_remote
581
			$qb->select('*')
582
				->from($this->shareTable);
583
584
			$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
585
586
			/**
587
			 * Reshares for this user are shares where they are the owner.
588
			 */
589 View Code Duplication
			if ($reshares === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
590
				//Special case for old shares created via the web UI
591
				$or1 = $qb->expr()->andX(
592
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
593
					$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...
594
				);
595
596
				$qb->andWhere(
597
					$qb->expr()->orX(
598
						$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
599
						$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...
600
					)
601
				);
602
			} else {
603
				$qb->andWhere(
604
					$qb->expr()->orX(
605
						$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
606
						$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...
607
					)
608
				);
609
			}
610
611
			$qb->andWhere($qb->expr()->in('file_source', $qb->createParameter('file_source_ids')));
612
			$qb->setParameter('file_source_ids', $nodeIdsChunk, IQueryBuilder::PARAM_INT_ARRAY);
613
614
			$qb->orderBy('id');
615
616
			$cursor = $qb->execute();
617
			while ($data = $cursor->fetch()) {
618
				$shares[] = $this->createShareObject($data);
619
			}
620
			$cursor->closeCursor();
621
		}
622
		
623
		return $shares;
624
	}
625
	
626
	/**
627
	 * @inheritdoc
628
	 */
629
	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
630
		$qb = $this->dbConnection->getQueryBuilder();
631
		$qb->select('*')
632
			->from($this->shareTable);
633
634
		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
635
636
		/**
637
		 * Reshares for this user are shares where they are the owner.
638
		 */
639 View Code Duplication
		if ($reshares === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
640
			//Special case for old shares created via the web UI
641
			$or1 = $qb->expr()->andX(
642
				$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
643
				$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...
644
			);
645
646
			$qb->andWhere(
647
				$qb->expr()->orX(
648
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
649
					$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...
650
				)
651
			);
652
		} else {
653
			$qb->andWhere(
654
				$qb->expr()->orX(
655
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
656
					$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...
657
				)
658
			);
659
		}
660
661
		if ($node !== null) {
662
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
663
		}
664
665
		if ($limit !== -1) {
666
			$qb->setMaxResults($limit);
667
		}
668
669
		$qb->setFirstResult($offset);
670
		$qb->orderBy('id');
671
672
		$cursor = $qb->execute();
673
		$shares = [];
674
		while ($data = $cursor->fetch()) {
675
			$shares[] = $this->createShareObject($data);
676
		}
677
		$cursor->closeCursor();
678
679
		return $shares;
680
	}
681
682
	/**
683
	 * @inheritdoc
684
	 */
685 View Code Duplication
	public function getShareById($id, $recipientId = null) {
686
		$qb = $this->dbConnection->getQueryBuilder();
687
688
		$qb->select('*')
689
			->from($this->shareTable)
690
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
691
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
692
		
693
		$cursor = $qb->execute();
694
		$data = $cursor->fetch();
695
		$cursor->closeCursor();
696
697
		if ($data === false) {
698
			throw new ShareNotFound();
699
		}
700
701
		try {
702
			$share = $this->createShareObject($data);
703
		} catch (InvalidShare $e) {
704
			throw new ShareNotFound();
705
		}
706
707
		return $share;
708
	}
709
710
	/**
711
	 * Get shares for a given path
712
	 *
713
	 * @param \OCP\Files\Node $path
714
	 * @return IShare[]
715
	 */
716 View Code Duplication
	public function getSharesByPath(Node $path) {
717
		$qb = $this->dbConnection->getQueryBuilder();
718
719
		$cursor = $qb->select('*')
720
			->from($this->shareTable)
721
			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
722
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
723
			->execute();
724
725
		$shares = [];
726
		while ($data = $cursor->fetch()) {
727
			$shares[] = $this->createShareObject($data);
728
		}
729
		$cursor->closeCursor();
730
731
		return $shares;
732
	}
733
734
	/**
735
	 * @inheritdoc
736
	 */
737
	public function getAllSharedWith($userId, $node) {
738
		return $this->getSharedWith($userId, self::SHARE_TYPE_REMOTE, $node, -1, 0);
739
	}
740
741
	/**
742
	 * @inheritdoc
743
	 */
744
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
745
		/** @var IShare[] $shares */
746
		$shares = [];
747
748
		//Get shares directly with this user
749
		$qb = $this->dbConnection->getQueryBuilder();
750
		$qb->select('*')
751
			->from($this->shareTable);
752
753
		// Order by id
754
		$qb->orderBy('id');
755
756
		// Set limit and offset
757
		if ($limit !== -1) {
758
			$qb->setMaxResults($limit);
759
		}
760
		$qb->setFirstResult($offset);
761
762
		$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
763
		$qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
764
765
		// Filter by node if provided
766
		if ($node !== null) {
767
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
768
		}
769
770
		$cursor = $qb->execute();
771
772
		while ($data = $cursor->fetch()) {
773
			$shares[] = $this->createShareObject($data);
774
		}
775
		$cursor->closeCursor();
776
777
		return $shares;
778
	}
779
780
	/**
781
	 * Get a share by token
782
	 *
783
	 * @param string $token
784
	 * @return IShare
785
	 * @throws ShareNotFound
786
	 */
787 View Code Duplication
	public function getShareByToken($token) {
788
		$qb = $this->dbConnection->getQueryBuilder();
789
790
		$cursor = $qb->select('*')
791
			->from($this->shareTable)
792
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
793
			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
794
			->execute();
795
796
		$data = $cursor->fetch();
797
798
		if ($data === false) {
799
			throw new ShareNotFound();
800
		}
801
802
		try {
803
			$share = $this->createShareObject($data);
804
		} catch (InvalidShare $e) {
805
			throw new ShareNotFound();
806
		}
807
808
		return $share;
809
	}
810
811
	/**
812
	 * get database row of a give share
813
	 *
814
	 * @param $id
815
	 * @return array
816
	 * @throws ShareNotFound
817
	 */
818 View Code Duplication
	private function getRawShare($id) {
819
820
		// Now fetch the inserted share and create a complete share object
821
		$qb = $this->dbConnection->getQueryBuilder();
822
		$qb->select('*')
823
			->from($this->shareTable)
824
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
825
826
		$cursor = $qb->execute();
827
		$data = $cursor->fetch();
828
		$cursor->closeCursor();
829
830
		if ($data === false) {
831
			throw new ShareNotFound;
832
		}
833
834
		return $data;
835
	}
836
837
	/**
838
	 * Create a share object from an database row
839
	 *
840
	 * @param array $data
841
	 * @return IShare
842
	 * @throws InvalidShare
843
	 * @throws ShareNotFound
844
	 */
845
	private function createShareObject($data) {
846
		$share = new Share($this->rootFolder, $this->userManager);
847
		$share->setId((int)$data['id'])
848
			->setShareType((int)$data['share_type'])
849
			->setPermissions((int)$data['permissions'])
850
			->setTarget($data['file_target'])
851
			->setMailSend((bool)$data['mail_send'])
852
			->setToken($data['token']);
853
854
		$shareTime = new \DateTime();
855
		$shareTime->setTimestamp((int)$data['stime']);
856
		$share->setShareTime($shareTime);
857
		$share->setSharedWith($data['share_with']);
858
859
		if ($data['uid_initiator'] !== null) {
860
			$share->setShareOwner($data['uid_owner']);
861
			$share->setSharedBy($data['uid_initiator']);
862
		} else {
863
			//OLD SHARE
864
			$share->setSharedBy($data['uid_owner']);
865
			$path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
866
867
			$owner = $path->getOwner();
868
			$share->setShareOwner($owner->getUID());
869
		}
870
871
		$share->setNodeId((int)$data['file_source']);
872
		$share->setNodeType($data['item_type']);
873
874
		$share->setProviderId($this->identifier());
875
876
		return $share;
877
	}
878
879
	/**
880
	 * Get the node with file $id for $user
881
	 *
882
	 * @param string $userId
883
	 * @param int $id
884
	 * @return \OCP\Files\File|\OCP\Files\Folder
885
	 * @throws InvalidShare
886
	 */
887
	private function getNode($userId, $id) {
888
		try {
889
			$userFolder = $this->rootFolder->getUserFolder($userId);
890
		} catch (NotFoundException $e) {
891
			throw new InvalidShare();
892
		}
893
894
		$nodes = $userFolder->getById($id);
895
896
		if (empty($nodes)) {
897
			throw new InvalidShare();
898
		}
899
900
		return $nodes[0];
901
	}
902
903
	/**
904
	 * A user is deleted from the system
905
	 * So clean up the relevant shares.
906
	 *
907
	 * @param string $uid
908
	 * @param int $shareType
909
	 */
910 View Code Duplication
	public function userDeleted($uid, $shareType) {
911
		//TODO: probabaly a good idea to send unshare info to remote servers
912
913
		$qb = $this->dbConnection->getQueryBuilder();
914
915
		$qb->delete($this->shareTable)
916
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE)))
917
			->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
918
			->execute();
919
	}
920
921
	/**
922
	 * This provider does not handle groups
923
	 *
924
	 * @param string $gid
925
	 */
926
	public function groupDeleted($gid) {
927
		// We don't handle groups here
928
		return;
929
	}
930
931
	/**
932
	 * This provider does not handle groups
933
	 *
934
	 * @param string $uid
935
	 * @param string $gid
936
	 */
937
	public function userDeletedFromGroup($uid, $gid) {
938
		// We don't handle groups here
939
		return;
940
	}
941
942
	/**
943
	 * check if users from other ownCloud instances are allowed to mount public links share by this instance
944
	 *
945
	 * @return bool
946
	 */
947
	public function isOutgoingServer2serverShareEnabled() {
948
		$result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes');
949
		return ($result === 'yes') ? true : false;
950
	}
951
952
	/**
953
	 * check if users are allowed to mount public links from other ownClouds
954
	 *
955
	 * @return bool
956
	 */
957
	public function isIncomingServer2serverShareEnabled() {
958
		$result = $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes');
959
		return ($result === 'yes') ? true : false;
960
	}
961
962
	/**
963
	 * @inheritdoc
964
	 */
965
	public function updateForRecipient(IShare $share, $recipient) {
966
		/*
967
		 * This function does nothing yet as it is just for outgoing
968
		 * federated shares.
969
		 */
970
		return $share;
971
	}
972
973
	/**
974
	 * @param int $remoteId
975
	 * @param string $shareToken
976
	 * @return mixed
977
	 */
978
	public function unshare($remoteId, $shareToken) {
979
		$query = $this->dbConnection->getQueryBuilder();
980
		$query->select('*')->from($this->externalShareTable)
981
			->where(
982
				$query->expr()->eq(
983
					'remote_id', $query->createNamedParameter($remoteId)
984
				)
985
			)
986
			->andWhere(
987
				$query->expr()->eq(
988
					'share_token',
989
					$query->createNamedParameter($shareToken)
990
				)
991
			);
992
		$shareRow = $query->execute()->fetch();
993
		if ($shareRow !== false) {
994
			$query = $this->dbConnection->getQueryBuilder();
995
			$query->delete($this->externalShareTable)
996
				->where(
997
					$query->expr()->eq(
998
						'remote_id',
999
						$query->createNamedParameter($shareRow['remote_id'])
1000
					)
1001
				)
1002
				->andWhere(
1003
					$query->expr()->eq(
1004
						'share_token',
1005
						$query->createNamedParameter($shareRow['share_token'])
1006
					)
1007
				);
1008
			$query->execute();
1009
		}
1010
		return $shareRow;
1011
	}
1012
1013
	/**
1014
	 * @param $remote
1015
	 * @param $token
1016
	 * @param $name
1017
	 * @param $owner
1018
	 * @param $shareWith
1019
	 * @param $remoteId
1020
	 *
1021
	 * @return int
1022
	 */
1023
	public function addShare($remote, $token, $name, $owner, $shareWith, $remoteId) {
1024
		\OC_Util::setupFS($shareWith);
1025
		$externalManager = new \OCA\Files_Sharing\External\Manager(
1026
			$this->dbConnection,
1027
			\OC\Files\Filesystem::getMountManager(),
1028
			\OC\Files\Filesystem::getLoader(),
1029
			\OC::$server->getNotificationManager(),
1030
			\OC::$server->getEventDispatcher(),
1031
			$shareWith
1032
		);
1033
		$externalManager->addShare(
1034
			$remote, $token, '', $name, $owner, false, $shareWith, $remoteId
1035
		);
1036
		return $this->dbConnection->lastInsertId("*PREFIX*{$this->externalShareTable}");
1037
	}
1038
}
1039