FederatedShareProvider   F
last analyzed

Complexity

Total Complexity 81

Size/Duplication

Total Lines 982
Duplicated Lines 21.59 %

Coupling/Cohesion

Components 2
Dependencies 22

Importance

Changes 0
Metric Value
dl 212
loc 982
rs 1.618
c 0
b 0
f 0
wmc 81
lcom 2
cbo 22

37 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 21 21 1
A identifier() 0 3 1
B create() 0 66 8
A createFederatedShare() 0 41 4
A askOwnerToReShare() 0 17 1
A getShareFromExternalShareTable() 0 13 3
A addShareToDB() 0 25 1
A update() 0 19 2
A sendPermissionUpdate() 10 10 2
A updateSuccessfulReShare() 7 7 1
A storeRemoteId() 0 11 1
A getRemoteId() 0 12 3
A move() 0 7 1
A getChildren() 18 18 2
A delete() 0 29 4
A revokeShare() 12 12 3
A removeShareFromTable() 0 3 1
A removeShareFromTableById() 10 10 1
A deleteFromSelf() 0 7 1
A getAllSharesBy() 21 51 4
B getSharesBy() 21 52 5
A getShareById() 24 24 3
A getSharesByPath() 17 17 2
A getAllSharedWith() 0 3 1
A getSharedWith() 0 35 4
A getShareByToken() 23 23 3
A getRawShare() 18 18 2
A createShareObject() 0 33 2
A getNode() 0 15 3
A userDeleted() 10 10 1
A groupDeleted() 0 4 1
A userDeletedFromGroup() 0 4 1
A isOutgoingServer2serverShareEnabled() 0 4 2
A isIncomingServer2serverShareEnabled() 0 4 2
A updateForRecipient() 0 7 1
A unshare() 0 34 2
A addShare() 0 15 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like FederatedShareProvider often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FederatedShareProvider, and based on these observations, apply Extract Interface, too.

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