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

FederatedShareProvider::create()   B

Complexity

Conditions 8
Paths 34

Size

Total Lines 70

Duplication

Lines 6
Ratio 8.57 %

Importance

Changes 0
Metric Value
cc 8
nc 34
nop 1
dl 6
loc 70
rs 7.4101
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @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