Completed
Push — master ( 86d952...cbfcfb )
by Morris
24:23
created

FederatedShareProvider::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 25

Duplication

Lines 25
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 11
dl 25
loc 25
rs 9.52
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\ICloudIdManager;
34
use OCP\DB\QueryBuilder\IQueryBuilder;
35
use OCP\Files\Folder;
36
use OCP\Files\IRootFolder;
37
use OCP\IConfig;
38
use OCP\IL10N;
39
use OCP\ILogger;
40
use OCP\IUserManager;
41
use OCP\Share\Exceptions\GenericShareException;
42
use OCP\Share\IShare;
43
use OCP\Share\IShareProvider;
44
use OC\Share20\Exception\InvalidShare;
45
use OCP\Share\Exceptions\ShareNotFound;
46
use OCP\Files\NotFoundException;
47
use OCP\IDBConnection;
48
use OCP\Files\Node;
49
50
/**
51
 * Class FederatedShareProvider
52
 *
53
 * @package OCA\FederatedFileSharing
54
 */
55
class FederatedShareProvider implements IShareProvider {
56
57
	const SHARE_TYPE_REMOTE = 6;
58
59
	/** @var IDBConnection */
60
	private $dbConnection;
61
62
	/** @var AddressHandler */
63
	private $addressHandler;
64
65
	/** @var Notifications */
66
	private $notifications;
67
68
	/** @var TokenHandler */
69
	private $tokenHandler;
70
71
	/** @var IL10N */
72
	private $l;
73
74
	/** @var ILogger */
75
	private $logger;
76
77
	/** @var IRootFolder */
78
	private $rootFolder;
79
80
	/** @var IConfig */
81
	private $config;
82
83
	/** @var string */
84
	private $externalShareTable = 'share_external';
85
86
	/** @var IUserManager */
87
	private $userManager;
88
89
	/** @var ICloudIdManager */
90
	private $cloudIdManager;
91
92
	/** @var \OCP\GlobalScale\IConfig */
93
	private $gsConfig;
94
95
	/**
96
	 * DefaultShareProvider constructor.
97
	 *
98
	 * @param IDBConnection $connection
99
	 * @param AddressHandler $addressHandler
100
	 * @param Notifications $notifications
101
	 * @param TokenHandler $tokenHandler
102
	 * @param IL10N $l10n
103
	 * @param ILogger $logger
104
	 * @param IRootFolder $rootFolder
105
	 * @param IConfig $config
106
	 * @param IUserManager $userManager
107
	 * @param ICloudIdManager $cloudIdManager
108
	 * @param \OCP\GlobalScale\IConfig $globalScaleConfig
109
	 */
110 View Code Duplication
	public function __construct(
111
			IDBConnection $connection,
112
			AddressHandler $addressHandler,
113
			Notifications $notifications,
114
			TokenHandler $tokenHandler,
115
			IL10N $l10n,
116
			ILogger $logger,
117
			IRootFolder $rootFolder,
118
			IConfig $config,
119
			IUserManager $userManager,
120
			ICloudIdManager $cloudIdManager,
121
			\OCP\GlobalScale\IConfig $globalScaleConfig
122
	) {
123
		$this->dbConnection = $connection;
124
		$this->addressHandler = $addressHandler;
125
		$this->notifications = $notifications;
126
		$this->tokenHandler = $tokenHandler;
127
		$this->l = $l10n;
128
		$this->logger = $logger;
129
		$this->rootFolder = $rootFolder;
130
		$this->config = $config;
131
		$this->userManager = $userManager;
132
		$this->cloudIdManager = $cloudIdManager;
133
		$this->gsConfig = $globalScaleConfig;
134
	}
135
136
	/**
137
	 * Return the identifier of this provider.
138
	 *
139
	 * @return string Containing only [a-zA-Z0-9]
140
	 */
141
	public function identifier() {
142
		return 'ocFederatedSharing';
143
	}
144
145
	/**
146
	 * Share a path
147
	 *
148
	 * @param IShare $share
149
	 * @return IShare The share object
150
	 * @throws ShareNotFound
151
	 * @throws \Exception
152
	 */
153
	public function create(IShare $share) {
154
155
		$shareWith = $share->getSharedWith();
156
		$itemSource = $share->getNodeId();
157
		$itemType = $share->getNodeType();
158
		$permissions = $share->getPermissions();
159
		$sharedBy = $share->getSharedBy();
160
161
		/*
162
		 * Check if file is not already shared with the remote user
163
		 */
164
		$alreadyShared = $this->getSharedWith($shareWith, self::SHARE_TYPE_REMOTE, $share->getNode(), 1, 0);
165 View Code Duplication
		if (!empty($alreadyShared)) {
166
			$message = 'Sharing %s failed, because this item is already shared with %s';
167
			$message_t = $this->l->t('Sharing %s failed, because this item is already shared with %s', array($share->getNode()->getName(), $shareWith));
168
			$this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
169
			throw new \Exception($message_t);
170
		}
171
172
173
		// don't allow federated shares if source and target server are the same
174
		$cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
175
		$currentServer = $this->addressHandler->generateRemoteURL();
176
		$currentUser = $sharedBy;
177
		if ($this->addressHandler->compareAddresses($cloudId->getUser(), $cloudId->getRemote(), $currentUser, $currentServer)) {
178
			$message = 'Not allowed to create a federated share with the same user.';
179
			$message_t = $this->l->t('Not allowed to create a federated share with the same user');
180
			$this->logger->debug($message, ['app' => 'Federated File Sharing']);
181
			throw new \Exception($message_t);
182
		}
183
184
185
		$share->setSharedWith($cloudId->getId());
186
187
		try {
188
			$remoteShare = $this->getShareFromExternalShareTable($share);
189
		} catch (ShareNotFound $e) {
190
			$remoteShare = null;
191
		}
192
193
		if ($remoteShare) {
194
			try {
195
				$ownerCloudId = $this->cloudIdManager->getCloudId($remoteShare['owner'], $remoteShare['remote']);
196
				$shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ownerCloudId->getId(), $permissions, 'tmp_token_' . time());
197
				$share->setId($shareId);
198
				list($token, $remoteId) = $this->askOwnerToReShare($shareWith, $share, $shareId);
199
				// remote share was create successfully if we get a valid token as return
200
				$send = is_string($token) && $token !== '';
201
			} catch (\Exception $e) {
202
				// fall back to old re-share behavior if the remote server
203
				// doesn't support flat re-shares (was introduced with Nextcloud 9.1)
204
				$this->removeShareFromTable($share);
205
				$shareId = $this->createFederatedShare($share);
206
			}
207
			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...
208
				$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...
209
				$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...
210
			} else {
211
				$this->removeShareFromTable($share);
212
				$message_t = $this->l->t('File is already shared with %s', [$shareWith]);
213
				throw new \Exception($message_t);
214
			}
215
216
		} else {
217
			$shareId = $this->createFederatedShare($share);
218
		}
219
220
		$data = $this->getRawShare($shareId);
221
		return $this->createShareObject($data);
222
	}
223
224
	/**
225
	 * create federated share and inform the recipient
226
	 *
227
	 * @param IShare $share
228
	 * @return int
229
	 * @throws ShareNotFound
230
	 * @throws \Exception
231
	 */
232
	protected function createFederatedShare(IShare $share) {
233
		$token = $this->tokenHandler->generateToken();
234
		$shareId = $this->addShareToDB(
235
			$share->getNodeId(),
236
			$share->getNodeType(),
237
			$share->getSharedWith(),
238
			$share->getSharedBy(),
239
			$share->getShareOwner(),
240
			$share->getPermissions(),
241
			$token
242
		);
243
244
		$failure = false;
245
246
		try {
247
			$sharedByFederatedId = $share->getSharedBy();
248
			if ($this->userManager->userExists($sharedByFederatedId)) {
249
				$cloudId = $this->cloudIdManager->getCloudId($sharedByFederatedId, $this->addressHandler->generateRemoteURL());
250
				$sharedByFederatedId = $cloudId->getId();
251
			}
252
			$ownerCloudId = $this->cloudIdManager->getCloudId($share->getShareOwner(), $this->addressHandler->generateRemoteURL());
253
			$send = $this->notifications->sendRemoteShare(
254
				$token,
255
				$share->getSharedWith(),
256
				$share->getNode()->getName(),
257
				$shareId,
258
				$share->getShareOwner(),
259
				$ownerCloudId->getId(),
260
				$share->getSharedBy(),
261
				$sharedByFederatedId
262
			);
263
264
			if ($send === false) {
265
				$failure = true;
266
			}
267
		} catch (\Exception $e) {
268
			$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...
269
				'message' => 'Failed to notify remote server of federated share, removing share.',
270
				'level' => ILogger::ERROR,
271
				'app' => 'federatedfilesharing',
272
			]);
273
			$failure = true;
274
		}
275
276
		if($failure) {
277
			$this->removeShareFromTableById($shareId);
278
			$message_t = $this->l->t('Sharing %s failed, could not find %s, maybe the server is currently unreachable or uses a self-signed certificate.',
279
				[$share->getNode()->getName(), $share->getSharedWith()]);
280
			throw new \Exception($message_t);
281
		}
282
283
		return $shareId;
284
285
	}
286
287
	/**
288
	 * @param string $shareWith
289
	 * @param IShare $share
290
	 * @param string $shareId internal share Id
291
	 * @return array
292
	 * @throws \Exception
293
	 */
294
	protected function askOwnerToReShare($shareWith, IShare $share, $shareId) {
295
296
		$remoteShare = $this->getShareFromExternalShareTable($share);
297
		$token = $remoteShare['share_token'];
298
		$remoteId = $remoteShare['remote_id'];
299
		$remote = $remoteShare['remote'];
300
301
		list($token, $remoteId) = $this->notifications->requestReShare(
302
			$token,
303
			$remoteId,
304
			$shareId,
305
			$remote,
306
			$shareWith,
307
			$share->getPermissions(),
308
			$share->getNode()->getName()
309
		);
310
311
		return [$token, $remoteId];
312
	}
313
314
	/**
315
	 * get federated share from the share_external table but exclude mounted link shares
316
	 *
317
	 * @param IShare $share
318
	 * @return array
319
	 * @throws ShareNotFound
320
	 */
321
	protected function getShareFromExternalShareTable(IShare $share) {
322
		$query = $this->dbConnection->getQueryBuilder();
323
		$query->select('*')->from($this->externalShareTable)
324
			->where($query->expr()->eq('user', $query->createNamedParameter($share->getShareOwner())))
325
			->andWhere($query->expr()->eq('mountpoint', $query->createNamedParameter($share->getTarget())));
326
		$result = $query->execute()->fetchAll();
327
328
		if (isset($result[0]) && (int)$result[0]['remote_id'] > 0) {
329
			return $result[0];
330
		}
331
332
		throw new ShareNotFound('share not found in share_external table');
333
	}
334
335
	/**
336
	 * add share to the database and return the ID
337
	 *
338
	 * @param int $itemSource
339
	 * @param string $itemType
340
	 * @param string $shareWith
341
	 * @param string $sharedBy
342
	 * @param string $uidOwner
343
	 * @param int $permissions
344
	 * @param string $token
345
	 * @return int
346
	 */
347
	private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token) {
348
		$qb = $this->dbConnection->getQueryBuilder();
349
		$qb->insert('share')
350
			->setValue('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE))
351
			->setValue('item_type', $qb->createNamedParameter($itemType))
352
			->setValue('item_source', $qb->createNamedParameter($itemSource))
353
			->setValue('file_source', $qb->createNamedParameter($itemSource))
354
			->setValue('share_with', $qb->createNamedParameter($shareWith))
355
			->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
356
			->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
357
			->setValue('permissions', $qb->createNamedParameter($permissions))
358
			->setValue('token', $qb->createNamedParameter($token))
359
			->setValue('stime', $qb->createNamedParameter(time()));
360
361
		/*
362
		 * Added to fix https://github.com/owncloud/core/issues/22215
363
		 * Can be removed once we get rid of ajax/share.php
364
		 */
365
		$qb->setValue('file_target', $qb->createNamedParameter(''));
366
367
		$qb->execute();
368
		$id = $qb->getLastInsertId();
369
370
		return (int)$id;
371
	}
372
373
	/**
374
	 * Update a share
375
	 *
376
	 * @param IShare $share
377
	 * @return IShare The share object
378
	 */
379
	public function update(IShare $share) {
380
		/*
381
		 * We allow updating the permissions of federated shares
382
		 */
383
		$qb = $this->dbConnection->getQueryBuilder();
384
			$qb->update('share')
385
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
386
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
387
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
388
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
389
				->execute();
390
391
		// send the updated permission to the owner/initiator, if they are not the same
392
		if ($share->getShareOwner() !== $share->getSharedBy()) {
393
			$this->sendPermissionUpdate($share);
394
		}
395
396
		return $share;
397
	}
398
399
	/**
400
	 * send the updated permission to the owner/initiator, if they are not the same
401
	 *
402
	 * @param IShare $share
403
	 * @throws ShareNotFound
404
	 * @throws \OC\HintException
405
	 */
406 View Code Duplication
	protected function sendPermissionUpdate(IShare $share) {
407
		$remoteId = $this->getRemoteId($share);
408
		// if the local user is the owner we send the permission change to the initiator
409
		if ($this->userManager->userExists($share->getShareOwner())) {
410
			list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
411
		} else { // ... if not we send the permission change to the owner
412
			list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
413
		}
414
		$this->notifications->sendPermissionChange($remote, $remoteId, $share->getToken(), $share->getPermissions());
415
	}
416
417
418
	/**
419
	 * update successful reShare with the correct token
420
	 *
421
	 * @param int $shareId
422
	 * @param string $token
423
	 */
424 View Code Duplication
	protected function updateSuccessfulReShare($shareId, $token) {
425
		$query = $this->dbConnection->getQueryBuilder();
426
		$query->update('share')
427
			->where($query->expr()->eq('id', $query->createNamedParameter($shareId)))
428
			->set('token', $query->createNamedParameter($token))
429
			->execute();
430
	}
431
432
	/**
433
	 * store remote ID in federated reShare table
434
	 *
435
	 * @param $shareId
436
	 * @param $remoteId
437
	 */
438
	public function storeRemoteId($shareId, $remoteId) {
439
		$query = $this->dbConnection->getQueryBuilder();
440
		$query->insert('federated_reshares')
441
			->values(
442
				[
443
					'share_id' =>  $query->createNamedParameter($shareId),
444
					'remote_id' => $query->createNamedParameter($remoteId),
445
				]
446
			);
447
		$query->execute();
448
	}
449
450
	/**
451
	 * get share ID on remote server for federated re-shares
452
	 *
453
	 * @param IShare $share
454
	 * @return int
455
	 * @throws ShareNotFound
456
	 */
457
	public function getRemoteId(IShare $share) {
458
		$query = $this->dbConnection->getQueryBuilder();
459
		$query->select('remote_id')->from('federated_reshares')
460
			->where($query->expr()->eq('share_id', $query->createNamedParameter((int)$share->getId())));
461
		$data = $query->execute()->fetch();
462
463
		if (!is_array($data) || !isset($data['remote_id'])) {
464
			throw new ShareNotFound();
465
		}
466
467
		return (int)$data['remote_id'];
468
	}
469
470
	/**
471
	 * @inheritdoc
472
	 */
473
	public function move(IShare $share, $recipient) {
474
		/*
475
		 * This function does nothing yet as it is just for outgoing
476
		 * federated shares.
477
		 */
478
		return $share;
479
	}
480
481
	/**
482
	 * Get all children of this share
483
	 *
484
	 * @param IShare $parent
485
	 * @return IShare[]
486
	 */
487 View Code Duplication
	public function getChildren(IShare $parent) {
488
		$children = [];
489
490
		$qb = $this->dbConnection->getQueryBuilder();
491
		$qb->select('*')
492
			->from('share')
493
			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
494
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
495
			->orderBy('id');
496
497
		$cursor = $qb->execute();
498
		while($data = $cursor->fetch()) {
499
			$children[] = $this->createShareObject($data);
500
		}
501
		$cursor->closeCursor();
502
503
		return $children;
504
	}
505
506
	/**
507
	 * Delete a share (owner unShares the file)
508
	 *
509
	 * @param IShare $share
510
	 * @throws ShareNotFound
511
	 * @throws \OC\HintException
512
	 */
513
	public function delete(IShare $share) {
514
515
		list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedWith());
516
517
		// if the local user is the owner we can send the unShare request directly...
518
		if ($this->userManager->userExists($share->getShareOwner())) {
519
			$this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken());
520
			$this->revokeShare($share, true);
521
		} else { // ... if not we need to correct ID for the unShare request
522
			$remoteId = $this->getRemoteId($share);
523
			$this->notifications->sendRemoteUnShare($remote, $remoteId, $share->getToken());
524
			$this->revokeShare($share, false);
525
		}
526
527
		// only remove the share when all messages are send to not lose information
528
		// about the share to early
529
		$this->removeShareFromTable($share);
530
	}
531
532
	/**
533
	 * in case of a re-share we need to send the other use (initiator or owner)
534
	 * a message that the file was unshared
535
	 *
536
	 * @param IShare $share
537
	 * @param bool $isOwner the user can either be the owner or the user who re-sahred it
538
	 * @throws ShareNotFound
539
	 * @throws \OC\HintException
540
	 */
541 View Code Duplication
	protected function revokeShare($share, $isOwner) {
542
		// also send a unShare request to the initiator, if this is a different user than the owner
543
		if ($share->getShareOwner() !== $share->getSharedBy()) {
544
			if ($isOwner) {
545
				list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
546
			} else {
547
				list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
548
			}
549
			$remoteId = $this->getRemoteId($share);
550
			$this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken());
551
		}
552
	}
553
554
	/**
555
	 * remove share from table
556
	 *
557
	 * @param IShare $share
558
	 */
559
	public function removeShareFromTable(IShare $share) {
560
		$this->removeShareFromTableById($share->getId());
561
	}
562
563
	/**
564
	 * remove share from table
565
	 *
566
	 * @param string $shareId
567
	 */
568
	private function removeShareFromTableById($shareId) {
569
		$qb = $this->dbConnection->getQueryBuilder();
570
		$qb->delete('share')
571
			->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)));
572
		$qb->execute();
573
574
		$qb->delete('federated_reshares')
575
			->where($qb->expr()->eq('share_id', $qb->createNamedParameter($shareId)));
576
		$qb->execute();
577
	}
578
579
	/**
580
	 * @inheritdoc
581
	 */
582
	public function deleteFromSelf(IShare $share, $recipient) {
583
		// nothing to do here. Technically deleteFromSelf in the context of federated
584
		// shares is a umount of a external storage. This is handled here
585
		// apps/files_sharing/lib/external/manager.php
586
		// TODO move this code over to this app
587
	}
588
589
	public function restore(IShare $share, string $recipient): IShare {
590
		throw new GenericShareException('not implemented');
591
	}
592
593
594 View Code Duplication
	public function getSharesInFolder($userId, Folder $node, $reshares) {
595
		$qb = $this->dbConnection->getQueryBuilder();
596
		$qb->select('*')
597
			->from('share', 's')
598
			->andWhere($qb->expr()->orX(
599
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
600
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
601
			))
602
			->andWhere(
603
				$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE))
604
			);
605
606
		/**
607
		 * Reshares for this user are shares where they are the owner.
608
		 */
609
		if ($reshares === false) {
610
			$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
611
		} else {
612
			$qb->andWhere(
613
				$qb->expr()->orX(
614
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
615
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
616
				)
617
			);
618
		}
619
620
		$qb->innerJoin('s', 'filecache' ,'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
621
		$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
622
623
		$qb->orderBy('id');
624
625
		$cursor = $qb->execute();
626
		$shares = [];
627
		while ($data = $cursor->fetch()) {
628
			$shares[$data['fileid']][] = $this->createShareObject($data);
629
		}
630
		$cursor->closeCursor();
631
632
		return $shares;
633
	}
634
635
	/**
636
	 * @inheritdoc
637
	 */
638 View Code Duplication
	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
639
		$qb = $this->dbConnection->getQueryBuilder();
640
		$qb->select('*')
641
			->from('share');
642
643
		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
644
645
		/**
646
		 * Reshares for this user are shares where they are the owner.
647
		 */
648
		if ($reshares === false) {
649
			//Special case for old shares created via the web UI
650
			$or1 = $qb->expr()->andX(
651
				$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
652
				$qb->expr()->isNull('uid_initiator')
653
			);
654
655
			$qb->andWhere(
656
				$qb->expr()->orX(
657
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
658
					$or1
659
				)
660
			);
661
		} else {
662
			$qb->andWhere(
663
				$qb->expr()->orX(
664
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
665
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
666
				)
667
			);
668
		}
669
670
		if ($node !== null) {
671
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
672
		}
673
674
		if ($limit !== -1) {
675
			$qb->setMaxResults($limit);
676
		}
677
678
		$qb->setFirstResult($offset);
679
		$qb->orderBy('id');
680
681
		$cursor = $qb->execute();
682
		$shares = [];
683
		while($data = $cursor->fetch()) {
684
			$shares[] = $this->createShareObject($data);
685
		}
686
		$cursor->closeCursor();
687
688
		return $shares;
689
	}
690
691
	/**
692
	 * @inheritdoc
693
	 */
694 View Code Duplication
	public function getShareById($id, $recipientId = null) {
695
		$qb = $this->dbConnection->getQueryBuilder();
696
697
		$qb->select('*')
698
			->from('share')
699
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
700
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
701
702
		$cursor = $qb->execute();
703
		$data = $cursor->fetch();
704
		$cursor->closeCursor();
705
706
		if ($data === false) {
707
			throw new ShareNotFound('Can not find share with ID: ' . $id);
708
		}
709
710
		try {
711
			$share = $this->createShareObject($data);
712
		} catch (InvalidShare $e) {
713
			throw new ShareNotFound();
714
		}
715
716
		return $share;
717
	}
718
719
	/**
720
	 * Get shares for a given path
721
	 *
722
	 * @param \OCP\Files\Node $path
723
	 * @return IShare[]
724
	 */
725 View Code Duplication
	public function getSharesByPath(Node $path) {
726
		$qb = $this->dbConnection->getQueryBuilder();
727
728
		$cursor = $qb->select('*')
729
			->from('share')
730
			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
731
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
732
			->execute();
733
734
		$shares = [];
735
		while($data = $cursor->fetch()) {
736
			$shares[] = $this->createShareObject($data);
737
		}
738
		$cursor->closeCursor();
739
740
		return $shares;
741
	}
742
743
	/**
744
	 * @inheritdoc
745
	 */
746 View Code Duplication
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
747
		/** @var IShare[] $shares */
748
		$shares = [];
749
750
		//Get shares directly with this user
751
		$qb = $this->dbConnection->getQueryBuilder();
752
		$qb->select('*')
753
			->from('share');
754
755
		// Order by id
756
		$qb->orderBy('id');
757
758
		// Set limit and offset
759
		if ($limit !== -1) {
760
			$qb->setMaxResults($limit);
761
		}
762
		$qb->setFirstResult($offset);
763
764
		$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
765
		$qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
766
767
		// Filter by node if provided
768
		if ($node !== null) {
769
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
770
		}
771
772
		$cursor = $qb->execute();
773
774
		while($data = $cursor->fetch()) {
775
			$shares[] = $this->createShareObject($data);
776
		}
777
		$cursor->closeCursor();
778
779
780
		return $shares;
781
	}
782
783
	/**
784
	 * Get a share by token
785
	 *
786
	 * @param string $token
787
	 * @return IShare
788
	 * @throws ShareNotFound
789
	 */
790 View Code Duplication
	public function getShareByToken($token) {
791
		$qb = $this->dbConnection->getQueryBuilder();
792
793
		$cursor = $qb->select('*')
794
			->from('share')
795
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
796
			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
797
			->execute();
798
799
		$data = $cursor->fetch();
800
801
		if ($data === false) {
802
			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
803
		}
804
805
		try {
806
			$share = $this->createShareObject($data);
807
		} catch (InvalidShare $e) {
808
			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
809
		}
810
811
		return $share;
812
	}
813
814
	/**
815
	 * get database row of a give share
816
	 *
817
	 * @param $id
818
	 * @return array
819
	 * @throws ShareNotFound
820
	 */
821 View Code Duplication
	private function getRawShare($id) {
822
823
		// Now fetch the inserted share and create a complete share object
824
		$qb = $this->dbConnection->getQueryBuilder();
825
		$qb->select('*')
826
			->from('share')
827
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
828
829
		$cursor = $qb->execute();
830
		$data = $cursor->fetch();
831
		$cursor->closeCursor();
832
833
		if ($data === false) {
834
			throw new ShareNotFound;
835
		}
836
837
		return $data;
838
	}
839
840
	/**
841
	 * Create a share object from an database row
842
	 *
843
	 * @param array $data
844
	 * @return IShare
845
	 * @throws InvalidShare
846
	 * @throws ShareNotFound
847
	 */
848
	private function createShareObject($data) {
849
850
		$share = new Share($this->rootFolder, $this->userManager);
851
		$share->setId((int)$data['id'])
852
			->setShareType((int)$data['share_type'])
853
			->setPermissions((int)$data['permissions'])
854
			->setTarget($data['file_target'])
855
			->setMailSend((bool)$data['mail_send'])
856
			->setToken($data['token']);
857
858
		$shareTime = new \DateTime();
859
		$shareTime->setTimestamp((int)$data['stime']);
860
		$share->setShareTime($shareTime);
861
		$share->setSharedWith($data['share_with']);
862
863
		if ($data['uid_initiator'] !== null) {
864
			$share->setShareOwner($data['uid_owner']);
865
			$share->setSharedBy($data['uid_initiator']);
866
		} else {
867
			//OLD SHARE
868
			$share->setSharedBy($data['uid_owner']);
869
			$path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
870
871
			$owner = $path->getOwner();
872
			$share->setShareOwner($owner->getUID());
873
		}
874
875
		$share->setNodeId((int)$data['file_source']);
876
		$share->setNodeType($data['item_type']);
877
878
		$share->setProviderId($this->identifier());
879
880
		return $share;
881
	}
882
883
	/**
884
	 * Get the node with file $id for $user
885
	 *
886
	 * @param string $userId
887
	 * @param int $id
888
	 * @return \OCP\Files\File|\OCP\Files\Folder
889
	 * @throws InvalidShare
890
	 */
891 View Code Duplication
	private function getNode($userId, $id) {
892
		try {
893
			$userFolder = $this->rootFolder->getUserFolder($userId);
894
		} catch (NotFoundException $e) {
895
			throw new InvalidShare();
896
		}
897
898
		$nodes = $userFolder->getById($id);
899
900
		if (empty($nodes)) {
901
			throw new InvalidShare();
902
		}
903
904
		return $nodes[0];
905
	}
906
907
	/**
908
	 * A user is deleted from the system
909
	 * So clean up the relevant shares.
910
	 *
911
	 * @param string $uid
912
	 * @param int $shareType
913
	 */
914 View Code Duplication
	public function userDeleted($uid, $shareType) {
915
		//TODO: probabaly a good idea to send unshare info to remote servers
916
917
		$qb = $this->dbConnection->getQueryBuilder();
918
919
		$qb->delete('share')
920
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE)))
921
			->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
922
			->execute();
923
	}
924
925
	/**
926
	 * This provider does not handle groups
927
	 *
928
	 * @param string $gid
929
	 */
930
	public function groupDeleted($gid) {
931
		// We don't handle groups here
932
	}
933
934
	/**
935
	 * This provider does not handle groups
936
	 *
937
	 * @param string $uid
938
	 * @param string $gid
939
	 */
940
	public function userDeletedFromGroup($uid, $gid) {
941
		// We don't handle groups here
942
	}
943
944
	/**
945
	 * check if users from other Nextcloud instances are allowed to mount public links share by this instance
946
	 *
947
	 * @return bool
948
	 */
949 View Code Duplication
	public function isOutgoingServer2serverShareEnabled() {
950
		if ($this->gsConfig->onlyInternalFederation()) {
951
			return false;
952
		}
953
		$result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes');
954
		return ($result === 'yes');
955
	}
956
957
	/**
958
	 * check if users are allowed to mount public links from other Nextclouds
959
	 *
960
	 * @return bool
961
	 */
962 View Code Duplication
	public function isIncomingServer2serverShareEnabled() {
963
		if ($this->gsConfig->onlyInternalFederation()) {
964
			return false;
965
		}
966
		$result = $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes');
967
		return ($result === 'yes');
968
	}
969
970
	/**
971
	 * Check if querying sharees on the lookup server is enabled
972
	 *
973
	 * @return bool
974
	 */
975
	public function isLookupServerQueriesEnabled() {
976
		// in a global scale setup we should always query the lookup server
977
		if ($this->gsConfig->isGlobalScaleEnabled()) {
978
			return true;
979
		}
980
		$result = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no');
981
		return ($result === 'yes');
982
	}
983
984
985
	/**
986
	 * Check if it is allowed to publish user specific data to the lookup server
987
	 *
988
	 * @return bool
989
	 */
990
	public function isLookupServerUploadEnabled() {
991
		// in a global scale setup the admin is responsible to keep the lookup server up-to-date
992
		if ($this->gsConfig->isGlobalScaleEnabled()) {
993
			return false;
994
		}
995
		$result = $this->config->getAppValue('files_sharing', 'lookupServerUploadEnabled', 'yes');
996
		return ($result === 'yes');
997
	}
998
999
	/**
1000
	 * @inheritdoc
1001
	 */
1002
	public function getAccessList($nodes, $currentAccess) {
1003
		$ids = [];
1004
		foreach ($nodes as $node) {
1005
			$ids[] = $node->getId();
1006
		}
1007
1008
		$qb = $this->dbConnection->getQueryBuilder();
1009
		$qb->select('share_with', 'token', 'file_source')
1010
			->from('share')
1011
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE)))
1012
			->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1013
			->andWhere($qb->expr()->orX(
1014
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1015
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1016
			));
1017
		$cursor = $qb->execute();
1018
1019
		if ($currentAccess === false) {
1020
			$remote = $cursor->fetch() !== false;
1021
			$cursor->closeCursor();
1022
1023
			return ['remote' => $remote];
1024
		}
1025
1026
		$remote = [];
1027
		while ($row = $cursor->fetch()) {
1028
			$remote[$row['share_with']] = [
1029
				'node_id' => $row['file_source'],
1030
				'token' => $row['token'],
1031
			];
1032
		}
1033
		$cursor->closeCursor();
1034
1035
		return ['remote' => $remote];
1036
	}
1037
}
1038