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

isOutgoingServer2serverGroupShareEnabled()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 7
Ratio 100 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 7
loc 7
rs 10
c 0
b 0
f 0
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