Completed
Push — master ( a89f9a...3036b1 )
by Morris
29:19 queued 12:13
created

FederatedShareProvider::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 27

Duplication

Lines 27
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 12
dl 27
loc 27
rs 9.488
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bjoern Schiessle <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 * @author Roeland Jago Douma <[email protected]>
12
 * @author Thomas Müller <[email protected]>
13
 *
14
 * @license AGPL-3.0
15
 *
16
 * This code is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License, version 3,
18
 * as published by the Free Software Foundation.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License, version 3,
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
27
 *
28
 */
29
30
namespace OCA\FederatedFileSharing;
31
32
use OC\Share20\Share;
33
use OCP\Federation\ICloudFederationProviderManager;
34
use OCP\Federation\ICloudIdManager;
35
use OCP\DB\QueryBuilder\IQueryBuilder;
36
use OCP\Files\Folder;
37
use OCP\Files\IRootFolder;
38
use OCP\IConfig;
39
use OCP\IL10N;
40
use OCP\ILogger;
41
use OCP\IUserManager;
42
use OCP\Share\Exceptions\GenericShareException;
43
use OCP\Share\IShare;
44
use OCP\Share\IShareProvider;
45
use OC\Share20\Exception\InvalidShare;
46
use OCP\Share\Exceptions\ShareNotFound;
47
use OCP\Files\NotFoundException;
48
use OCP\IDBConnection;
49
use OCP\Files\Node;
50
51
/**
52
 * Class FederatedShareProvider
53
 *
54
 * @package OCA\FederatedFileSharing
55
 */
56
class FederatedShareProvider implements IShareProvider {
57
58
	const SHARE_TYPE_REMOTE = 6;
59
60
	/** @var IDBConnection */
61
	private $dbConnection;
62
63
	/** @var AddressHandler */
64
	private $addressHandler;
65
66
	/** @var Notifications */
67
	private $notifications;
68
69
	/** @var TokenHandler */
70
	private $tokenHandler;
71
72
	/** @var IL10N */
73
	private $l;
74
75
	/** @var ILogger */
76
	private $logger;
77
78
	/** @var IRootFolder */
79
	private $rootFolder;
80
81
	/** @var IConfig */
82
	private $config;
83
84
	/** @var string */
85
	private $externalShareTable = 'share_external';
86
87
	/** @var IUserManager */
88
	private $userManager;
89
90
	/** @var ICloudIdManager */
91
	private $cloudIdManager;
92
93
	/** @var \OCP\GlobalScale\IConfig */
94
	private $gsConfig;
95
96
	/** @var ICloudFederationProviderManager */
97
	private $cloudFederationProviderManager;
98
99
	/** @var array list of supported share types */
100
	private $supportedShareType = [\OCP\Share::SHARE_TYPE_REMOTE_GROUP, \OCP\Share::SHARE_TYPE_REMOTE];
101
102
	/**
103
	 * DefaultShareProvider constructor.
104
	 *
105
	 * @param IDBConnection $connection
106
	 * @param AddressHandler $addressHandler
107
	 * @param Notifications $notifications
108
	 * @param TokenHandler $tokenHandler
109
	 * @param IL10N $l10n
110
	 * @param ILogger $logger
111
	 * @param IRootFolder $rootFolder
112
	 * @param IConfig $config
113
	 * @param IUserManager $userManager
114
	 * @param ICloudIdManager $cloudIdManager
115
	 * @param \OCP\GlobalScale\IConfig $globalScaleConfig
116
	 * @param ICloudFederationProviderManager $cloudFederationProviderManager
117
	 */
118 View Code Duplication
	public function __construct(
119
			IDBConnection $connection,
120
			AddressHandler $addressHandler,
121
			Notifications $notifications,
122
			TokenHandler $tokenHandler,
123
			IL10N $l10n,
124
			ILogger $logger,
125
			IRootFolder $rootFolder,
126
			IConfig $config,
127
			IUserManager $userManager,
128
			ICloudIdManager $cloudIdManager,
129
			\OCP\GlobalScale\IConfig $globalScaleConfig,
130
			ICloudFederationProviderManager $cloudFederationProviderManager
131
	) {
132
		$this->dbConnection = $connection;
133
		$this->addressHandler = $addressHandler;
134
		$this->notifications = $notifications;
135
		$this->tokenHandler = $tokenHandler;
136
		$this->l = $l10n;
137
		$this->logger = $logger;
138
		$this->rootFolder = $rootFolder;
139
		$this->config = $config;
140
		$this->userManager = $userManager;
141
		$this->cloudIdManager = $cloudIdManager;
142
		$this->gsConfig = $globalScaleConfig;
143
		$this->cloudFederationProviderManager = $cloudFederationProviderManager;
144
	}
145
146
	/**
147
	 * Return the identifier of this provider.
148
	 *
149
	 * @return string Containing only [a-zA-Z0-9]
150
	 */
151
	public function identifier() {
152
		return 'ocFederatedSharing';
153
	}
154
155
	/**
156
	 * Share a path
157
	 *
158
	 * @param IShare $share
159
	 * @return IShare The share object
160
	 * @throws ShareNotFound
161
	 * @throws \Exception
162
	 */
163
	public function create(IShare $share) {
164
165
		$shareWith = $share->getSharedWith();
166
		$itemSource = $share->getNodeId();
167
		$itemType = $share->getNodeType();
168
		$permissions = $share->getPermissions();
169
		$sharedBy = $share->getSharedBy();
170
		$shareType = $share->getShareType();
171
172
		if ($shareType === \OCP\Share::SHARE_TYPE_REMOTE_GROUP &&
173
			!$this->isOutgoingServer2serverGroupShareEnabled()
174
		) {
175
			$message = 'It is not allowed to send federated group shares from this server.';
176
			$message_t = $this->l->t('It is not allowed to send federated group shares from this server.');
177
			$this->logger->debug($message, ['app' => 'Federated File Sharing']);
178
			throw new \Exception($message_t);
179
		}
180
181
		/*
182
		 * Check if file is not already shared with the remote user
183
		 */
184
		$alreadyShared = $this->getSharedWith($shareWith, \OCP\Share::SHARE_TYPE_REMOTE, $share->getNode(), 1, 0);
185
		$alreadySharedGroup = $this->getSharedWith($shareWith, \OCP\Share::SHARE_TYPE_REMOTE_GROUP, $share->getNode(), 1, 0);
186 View Code Duplication
		if (!empty($alreadyShared) || !empty($alreadySharedGroup)) {
187
			$message = 'Sharing %s failed, because this item is already shared with %s';
188
			$message_t = $this->l->t('Sharing %s failed, because this item is already shared with %s', array($share->getNode()->getName(), $shareWith));
189
			$this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
190
			throw new \Exception($message_t);
191
		}
192
193
194
		// don't allow federated shares if source and target server are the same
195
		$cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
196
		$currentServer = $this->addressHandler->generateRemoteURL();
197
		$currentUser = $sharedBy;
198
		if ($this->addressHandler->compareAddresses($cloudId->getUser(), $cloudId->getRemote(), $currentUser, $currentServer)) {
199
			$message = 'Not allowed to create a federated share with the same user.';
200
			$message_t = $this->l->t('Not allowed to create a federated share with the same user');
201
			$this->logger->debug($message, ['app' => 'Federated File Sharing']);
202
			throw new \Exception($message_t);
203
		}
204
205
206
		$share->setSharedWith($cloudId->getId());
207
208
		try {
209
			$remoteShare = $this->getShareFromExternalShareTable($share);
210
		} catch (ShareNotFound $e) {
211
			$remoteShare = null;
212
		}
213
214
		if ($remoteShare) {
215
			try {
216
				$ownerCloudId = $this->cloudIdManager->getCloudId($remoteShare['owner'], $remoteShare['remote']);
217
				$shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ownerCloudId->getId(), $permissions, 'tmp_token_' . time(), $shareType);
218
				$share->setId($shareId);
219
				list($token, $remoteId) = $this->askOwnerToReShare($shareWith, $share, $shareId);
220
				// remote share was create successfully if we get a valid token as return
221
				$send = is_string($token) && $token !== '';
222
			} catch (\Exception $e) {
223
				// fall back to old re-share behavior if the remote server
224
				// doesn't support flat re-shares (was introduced with Nextcloud 9.1)
225
				$this->removeShareFromTable($share);
226
				$shareId = $this->createFederatedShare($share);
227
			}
228
			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...
229
				$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...
230
				$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...
231
			} else {
232
				$this->removeShareFromTable($share);
233
				$message_t = $this->l->t('File is already shared with %s', [$shareWith]);
234
				throw new \Exception($message_t);
235
			}
236
237
		} else {
238
			$shareId = $this->createFederatedShare($share);
239
		}
240
241
		$data = $this->getRawShare($shareId);
242
		return $this->createShareObject($data);
243
	}
244
245
	/**
246
	 * create federated share and inform the recipient
247
	 *
248
	 * @param IShare $share
249
	 * @return int
250
	 * @throws ShareNotFound
251
	 * @throws \Exception
252
	 */
253
	protected function createFederatedShare(IShare $share) {
254
		$token = $this->tokenHandler->generateToken();
255
		$shareId = $this->addShareToDB(
256
			$share->getNodeId(),
257
			$share->getNodeType(),
258
			$share->getSharedWith(),
259
			$share->getSharedBy(),
260
			$share->getShareOwner(),
261
			$share->getPermissions(),
262
			$token,
263
			$share->getShareType()
264
		);
265
266
		$failure = false;
267
268
		try {
269
			$sharedByFederatedId = $share->getSharedBy();
270
			if ($this->userManager->userExists($sharedByFederatedId)) {
271
				$cloudId = $this->cloudIdManager->getCloudId($sharedByFederatedId, $this->addressHandler->generateRemoteURL());
272
				$sharedByFederatedId = $cloudId->getId();
273
			}
274
			$ownerCloudId = $this->cloudIdManager->getCloudId($share->getShareOwner(), $this->addressHandler->generateRemoteURL());
275
			$send = $this->notifications->sendRemoteShare(
276
				$token,
277
				$share->getSharedWith(),
278
				$share->getNode()->getName(),
279
				$shareId,
280
				$share->getShareOwner(),
281
				$ownerCloudId->getId(),
282
				$share->getSharedBy(),
283
				$sharedByFederatedId,
284
				$share->getShareType()
285
			);
286
287
			if ($send === false) {
288
				$failure = true;
289
			}
290
		} catch (\Exception $e) {
291
			$this->logger->logException($e, [
0 ignored issues
show
Documentation introduced by
$e is of type object<Exception>, but the function expects a object<Throwable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

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