Completed
Push — master ( c70de1...74fccf )
by Björn
18:31
created

FederatedShareProvider::getRawShare()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 11

Duplication

Lines 18
Ratio 100 %

Importance

Changes 0
Metric Value
cc 2
eloc 11
nc 2
nop 1
dl 18
loc 18
rs 9.4285
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 Robin Appelman <[email protected]>
8
 * @author Roeland Jago Douma <[email protected]>
9
 * @author Thomas Müller <[email protected]>
10
 *
11
 * @license AGPL-3.0
12
 *
13
 * This code is free software: you can redistribute it and/or modify
14
 * it under the terms of the GNU Affero General Public License, version 3,
15
 * as published by the Free Software Foundation.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License, version 3,
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
24
 *
25
 */
26
27
namespace OCA\FederatedFileSharing;
28
29
use OC\Share20\Share;
30
use OCP\Federation\ICloudIdManager;
31
use OCP\DB\QueryBuilder\IQueryBuilder;
32
use OCP\Files\Folder;
33
use OCP\Files\IRootFolder;
34
use OCP\IConfig;
35
use OCP\IL10N;
36
use OCP\ILogger;
37
use OCP\IUserManager;
38
use OCP\Share\IShare;
39
use OCP\Share\IShareProvider;
40
use OC\Share20\Exception\InvalidShare;
41
use OCP\Share\Exceptions\ShareNotFound;
42
use OCP\Files\NotFoundException;
43
use OCP\IDBConnection;
44
use OCP\Files\Node;
45
46
/**
47
 * Class FederatedShareProvider
48
 *
49
 * @package OCA\FederatedFileSharing
50
 */
51
class FederatedShareProvider implements IShareProvider {
52
53
	const SHARE_TYPE_REMOTE = 6;
54
55
	/** @var IDBConnection */
56
	private $dbConnection;
57
58
	/** @var AddressHandler */
59
	private $addressHandler;
60
61
	/** @var Notifications */
62
	private $notifications;
63
64
	/** @var TokenHandler */
65
	private $tokenHandler;
66
67
	/** @var IL10N */
68
	private $l;
69
70
	/** @var ILogger */
71
	private $logger;
72
73
	/** @var IRootFolder */
74
	private $rootFolder;
75
76
	/** @var IConfig */
77
	private $config;
78
79
	/** @var string */
80
	private $externalShareTable = 'share_external';
81
82
	/** @var IUserManager */
83
	private $userManager;
84
85
	/** @var ICloudIdManager */
86
	private $cloudIdManager;
87
88
	/** @var \OCP\GlobalScale\IConfig */
89
	private $gsConfig;
90
91
	/**
92
	 * DefaultShareProvider constructor.
93
	 *
94
	 * @param IDBConnection $connection
95
	 * @param AddressHandler $addressHandler
96
	 * @param Notifications $notifications
97
	 * @param TokenHandler $tokenHandler
98
	 * @param IL10N $l10n
99
	 * @param ILogger $logger
100
	 * @param IRootFolder $rootFolder
101
	 * @param IConfig $config
102
	 * @param IUserManager $userManager
103
	 * @param ICloudIdManager $cloudIdManager
104
	 * @param \OCP\GlobalScale\IConfig $globalScaleConfig
105
	 */
106
	public function __construct(
107
			IDBConnection $connection,
108
			AddressHandler $addressHandler,
109
			Notifications $notifications,
110
			TokenHandler $tokenHandler,
111
			IL10N $l10n,
112
			ILogger $logger,
113
			IRootFolder $rootFolder,
114
			IConfig $config,
115
			IUserManager $userManager,
116
			ICloudIdManager $cloudIdManager,
117
			\OCP\GlobalScale\IConfig $globalScaleConfig
118
	) {
119
		$this->dbConnection = $connection;
120
		$this->addressHandler = $addressHandler;
121
		$this->notifications = $notifications;
122
		$this->tokenHandler = $tokenHandler;
123
		$this->l = $l10n;
124
		$this->logger = $logger;
125
		$this->rootFolder = $rootFolder;
126
		$this->config = $config;
127
		$this->userManager = $userManager;
128
		$this->cloudIdManager = $cloudIdManager;
129
		$this->gsConfig = $globalScaleConfig;
130
	}
131
132
	/**
133
	 * Return the identifier of this provider.
134
	 *
135
	 * @return string Containing only [a-zA-Z0-9]
136
	 */
137
	public function identifier() {
138
		return 'ocFederatedSharing';
139
	}
140
141
	/**
142
	 * Share a path
143
	 *
144
	 * @param IShare $share
145
	 * @return IShare The share object
146
	 * @throws ShareNotFound
147
	 * @throws \Exception
148
	 */
149
	public function create(IShare $share) {
150
151
		$shareWith = $share->getSharedWith();
152
		$itemSource = $share->getNodeId();
153
		$itemType = $share->getNodeType();
154
		$permissions = $share->getPermissions();
155
		$sharedBy = $share->getSharedBy();
156
157
		/*
158
		 * Check if file is not already shared with the remote user
159
		 */
160
		$alreadyShared = $this->getSharedWith($shareWith, self::SHARE_TYPE_REMOTE, $share->getNode(), 1, 0);
161 View Code Duplication
		if (!empty($alreadyShared)) {
162
			$message = 'Sharing %s failed, because this item is already shared with %s';
163
			$message_t = $this->l->t('Sharing %s failed, because this item is already shared with %s', array($share->getNode()->getName(), $shareWith));
164
			$this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
165
			throw new \Exception($message_t);
166
		}
167
168
169
		// don't allow federated shares if source and target server are the same
170
		$cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
171
		$currentServer = $this->addressHandler->generateRemoteURL();
172
		$currentUser = $sharedBy;
173
		if ($this->addressHandler->compareAddresses($cloudId->getUser(), $cloudId->getRemote(), $currentUser, $currentServer)) {
174
			$message = 'Not allowed to create a federated share with the same user.';
175
			$message_t = $this->l->t('Not allowed to create a federated share with the same user');
176
			$this->logger->debug($message, ['app' => 'Federated File Sharing']);
177
			throw new \Exception($message_t);
178
		}
179
180
181
		$share->setSharedWith($cloudId->getId());
182
183
		try {
184
			$remoteShare = $this->getShareFromExternalShareTable($share);
185
		} catch (ShareNotFound $e) {
186
			$remoteShare = null;
187
		}
188
189
		if ($remoteShare) {
190
			try {
191
				$ownerCloudId = $this->cloudIdManager->getCloudId($remoteShare['owner'], $remoteShare['remote']);
192
				$shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ownerCloudId->getId(), $permissions, 'tmp_token_' . time());
193
				$share->setId($shareId);
194
				list($token, $remoteId) = $this->askOwnerToReShare($shareWith, $share, $shareId);
195
				// remote share was create successfully if we get a valid token as return
196
				$send = is_string($token) && $token !== '';
197
			} catch (\Exception $e) {
198
				// fall back to old re-share behavior if the remote server
199
				// doesn't support flat re-shares (was introduced with Nextcloud 9.1)
200
				$this->removeShareFromTable($share);
201
				$shareId = $this->createFederatedShare($share);
202
			}
203
			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...
204
				$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...
205
				$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...
206
			} else {
207
				$this->removeShareFromTable($share);
208
				$message_t = $this->l->t('File is already shared with %s', [$shareWith]);
209
				throw new \Exception($message_t);
210
			}
211
212
		} else {
213
			$shareId = $this->createFederatedShare($share);
214
		}
215
216
		$data = $this->getRawShare($shareId);
217
		return $this->createShareObject($data);
218
	}
219
220
	/**
221
	 * create federated share and inform the recipient
222
	 *
223
	 * @param IShare $share
224
	 * @return int
225
	 * @throws ShareNotFound
226
	 * @throws \Exception
227
	 */
228
	protected function createFederatedShare(IShare $share) {
229
		$token = $this->tokenHandler->generateToken();
230
		$shareId = $this->addShareToDB(
231
			$share->getNodeId(),
232
			$share->getNodeType(),
233
			$share->getSharedWith(),
234
			$share->getSharedBy(),
235
			$share->getShareOwner(),
236
			$share->getPermissions(),
237
			$token
238
		);
239
240
		$failure = false;
241
242
		try {
243
			$sharedByFederatedId = $share->getSharedBy();
244
			if ($this->userManager->userExists($sharedByFederatedId)) {
245
				$cloudId = $this->cloudIdManager->getCloudId($sharedByFederatedId, $this->addressHandler->generateRemoteURL());
246
				$sharedByFederatedId = $cloudId->getId();
247
			}
248
			$ownerCloudId = $this->cloudIdManager->getCloudId($share->getShareOwner(), $this->addressHandler->generateRemoteURL());
249
			$send = $this->notifications->sendRemoteShare(
250
				$token,
251
				$share->getSharedWith(),
252
				$share->getNode()->getName(),
253
				$shareId,
254
				$share->getShareOwner(),
255
				$ownerCloudId->getId(),
256
				$share->getSharedBy(),
257
				$sharedByFederatedId
258
			);
259
260
			if ($send === false) {
261
				$failure = true;
262
			}
263
		} catch (\Exception $e) {
264
			$this->logger->error('Failed to notify remote server of federated share, removing share (' . $e->getMessage() . ')');
265
			$failure = true;
266
		}
267
268
		if($failure) {
269
			$this->removeShareFromTableById($shareId);
270
			$message_t = $this->l->t('Sharing %s failed, could not find %s, maybe the server is currently unreachable or uses a self-signed certificate.',
271
				[$share->getNode()->getName(), $share->getSharedWith()]);
272
			throw new \Exception($message_t);
273
		}
274
275
		return $shareId;
276
277
	}
278
279
	/**
280
	 * @param string $shareWith
281
	 * @param IShare $share
282
	 * @param string $shareId internal share Id
283
	 * @return array
284
	 * @throws \Exception
285
	 */
286
	protected function askOwnerToReShare($shareWith, IShare $share, $shareId) {
287
288
		$remoteShare = $this->getShareFromExternalShareTable($share);
289
		$token = $remoteShare['share_token'];
290
		$remoteId = $remoteShare['remote_id'];
291
		$remote = $remoteShare['remote'];
292
293
		list($token, $remoteId) = $this->notifications->requestReShare(
294
			$token,
295
			$remoteId,
296
			$shareId,
297
			$remote,
298
			$shareWith,
299
			$share->getPermissions()
300
		);
301
302
		return [$token, $remoteId];
303
	}
304
305
	/**
306
	 * get federated share from the share_external table but exclude mounted link shares
307
	 *
308
	 * @param IShare $share
309
	 * @return array
310
	 * @throws ShareNotFound
311
	 */
312
	protected function getShareFromExternalShareTable(IShare $share) {
313
		$query = $this->dbConnection->getQueryBuilder();
314
		$query->select('*')->from($this->externalShareTable)
315
			->where($query->expr()->eq('user', $query->createNamedParameter($share->getShareOwner())))
316
			->andWhere($query->expr()->eq('mountpoint', $query->createNamedParameter($share->getTarget())));
317
		$result = $query->execute()->fetchAll();
318
319
		if (isset($result[0]) && (int)$result[0]['remote_id'] > 0) {
320
			return $result[0];
321
		}
322
323
		throw new ShareNotFound('share not found in share_external table');
324
	}
325
326
	/**
327
	 * add share to the database and return the ID
328
	 *
329
	 * @param int $itemSource
330
	 * @param string $itemType
331
	 * @param string $shareWith
332
	 * @param string $sharedBy
333
	 * @param string $uidOwner
334
	 * @param int $permissions
335
	 * @param string $token
336
	 * @return int
337
	 */
338
	private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token) {
339
		$qb = $this->dbConnection->getQueryBuilder();
340
		$qb->insert('share')
341
			->setValue('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE))
342
			->setValue('item_type', $qb->createNamedParameter($itemType))
343
			->setValue('item_source', $qb->createNamedParameter($itemSource))
344
			->setValue('file_source', $qb->createNamedParameter($itemSource))
345
			->setValue('share_with', $qb->createNamedParameter($shareWith))
346
			->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
347
			->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
348
			->setValue('permissions', $qb->createNamedParameter($permissions))
349
			->setValue('token', $qb->createNamedParameter($token))
350
			->setValue('stime', $qb->createNamedParameter(time()));
351
352
		/*
353
		 * Added to fix https://github.com/owncloud/core/issues/22215
354
		 * Can be removed once we get rid of ajax/share.php
355
		 */
356
		$qb->setValue('file_target', $qb->createNamedParameter(''));
357
358
		$qb->execute();
359
		$id = $qb->getLastInsertId();
360
361
		return (int)$id;
362
	}
363
364
	/**
365
	 * Update a share
366
	 *
367
	 * @param IShare $share
368
	 * @return IShare The share object
369
	 */
370
	public function update(IShare $share) {
371
		/*
372
		 * We allow updating the permissions of federated shares
373
		 */
374
		$qb = $this->dbConnection->getQueryBuilder();
375
			$qb->update('share')
376
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
377
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
378
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
379
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
380
				->execute();
381
382
		// send the updated permission to the owner/initiator, if they are not the same
383
		if ($share->getShareOwner() !== $share->getSharedBy()) {
384
			$this->sendPermissionUpdate($share);
385
		}
386
387
		return $share;
388
	}
389
390
	/**
391
	 * send the updated permission to the owner/initiator, if they are not the same
392
	 *
393
	 * @param IShare $share
394
	 * @throws ShareNotFound
395
	 * @throws \OC\HintException
396
	 */
397 View Code Duplication
	protected function sendPermissionUpdate(IShare $share) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
398
		$remoteId = $this->getRemoteId($share);
399
		// if the local user is the owner we send the permission change to the initiator
400
		if ($this->userManager->userExists($share->getShareOwner())) {
401
			list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
402
		} else { // ... if not we send the permission change to the owner
403
			list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
404
		}
405
		$this->notifications->sendPermissionChange($remote, $remoteId, $share->getToken(), $share->getPermissions());
406
	}
407
408
409
	/**
410
	 * update successful reShare with the correct token
411
	 *
412
	 * @param int $shareId
413
	 * @param string $token
414
	 */
415 View Code Duplication
	protected function updateSuccessfulReShare($shareId, $token) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
416
		$query = $this->dbConnection->getQueryBuilder();
417
		$query->update('share')
418
			->where($query->expr()->eq('id', $query->createNamedParameter($shareId)))
419
			->set('token', $query->createNamedParameter($token))
420
			->execute();
421
	}
422
423
	/**
424
	 * store remote ID in federated reShare table
425
	 *
426
	 * @param $shareId
427
	 * @param $remoteId
428
	 */
429
	public function storeRemoteId($shareId, $remoteId) {
430
		$query = $this->dbConnection->getQueryBuilder();
431
		$query->insert('federated_reshares')
432
			->values(
433
				[
434
					'share_id' =>  $query->createNamedParameter($shareId),
435
					'remote_id' => $query->createNamedParameter($remoteId),
436
				]
437
			);
438
		$query->execute();
439
	}
440
441
	/**
442
	 * get share ID on remote server for federated re-shares
443
	 *
444
	 * @param IShare $share
445
	 * @return int
446
	 * @throws ShareNotFound
447
	 */
448
	public function getRemoteId(IShare $share) {
449
		$query = $this->dbConnection->getQueryBuilder();
450
		$query->select('remote_id')->from('federated_reshares')
451
			->where($query->expr()->eq('share_id', $query->createNamedParameter((int)$share->getId())));
452
		$data = $query->execute()->fetch();
453
454
		if (!is_array($data) || !isset($data['remote_id'])) {
455
			throw new ShareNotFound();
456
		}
457
458
		return (int)$data['remote_id'];
459
	}
460
461
	/**
462
	 * @inheritdoc
463
	 */
464
	public function move(IShare $share, $recipient) {
465
		/*
466
		 * This function does nothing yet as it is just for outgoing
467
		 * federated shares.
468
		 */
469
		return $share;
470
	}
471
472
	/**
473
	 * Get all children of this share
474
	 *
475
	 * @param IShare $parent
476
	 * @return IShare[]
477
	 */
478 View Code Duplication
	public function getChildren(IShare $parent) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
479
		$children = [];
480
481
		$qb = $this->dbConnection->getQueryBuilder();
482
		$qb->select('*')
483
			->from('share')
484
			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
485
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
486
			->orderBy('id');
487
488
		$cursor = $qb->execute();
489
		while($data = $cursor->fetch()) {
490
			$children[] = $this->createShareObject($data);
491
		}
492
		$cursor->closeCursor();
493
494
		return $children;
495
	}
496
497
	/**
498
	 * Delete a share (owner unShares the file)
499
	 *
500
	 * @param IShare $share
501
	 */
502
	public function delete(IShare $share) {
503
504
		list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedWith());
505
506
		$isOwner = false;
507
508
		$this->removeShareFromTable($share);
509
510
		// if the local user is the owner we can send the unShare request directly...
511
		if ($this->userManager->userExists($share->getShareOwner())) {
512
			$this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken());
513
			$this->revokeShare($share, true);
514
			$isOwner = true;
515
		} else { // ... if not we need to correct ID for the unShare request
516
			$remoteId = $this->getRemoteId($share);
517
			$this->notifications->sendRemoteUnShare($remote, $remoteId, $share->getToken());
518
			$this->revokeShare($share, false);
519
		}
520
521
		// send revoke notification to the other user, if initiator and owner are not the same user
522
		if ($share->getShareOwner() !== $share->getSharedBy()) {
523
			$remoteId = $this->getRemoteId($share);
524
			if ($isOwner) {
525
				list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
526
			} else {
527
				list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
528
			}
529
			$this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken());
530
		}
531
	}
532
533
	/**
534
	 * in case of a re-share we need to send the other use (initiator or owner)
535
	 * a message that the file was unshared
536
	 *
537
	 * @param IShare $share
538
	 * @param bool $isOwner the user can either be the owner or the user who re-sahred it
539
	 * @throws ShareNotFound
540
	 * @throws \OC\HintException
541
	 */
542 View Code Duplication
	protected function revokeShare($share, $isOwner) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
543
		// also send a unShare request to the initiator, if this is a different user than the owner
544
		if ($share->getShareOwner() !== $share->getSharedBy()) {
545
			if ($isOwner) {
546
				list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
547
			} else {
548
				list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
549
			}
550
			$remoteId = $this->getRemoteId($share);
551
			$this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken());
552
		}
553
	}
554
555
	/**
556
	 * remove share from table
557
	 *
558
	 * @param IShare $share
559
	 */
560
	public function removeShareFromTable(IShare $share) {
561
		$this->removeShareFromTableById($share->getId());
562
	}
563
564
	/**
565
	 * remove share from table
566
	 *
567
	 * @param string $shareId
568
	 */
569
	private function removeShareFromTableById($shareId) {
570
		$qb = $this->dbConnection->getQueryBuilder();
571
		$qb->delete('share')
572
			->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)));
573
		$qb->execute();
574
575
		$qb->delete('federated_reshares')
576
			->where($qb->expr()->eq('share_id', $qb->createNamedParameter($shareId)));
577
		$qb->execute();
578
	}
579
580
	/**
581
	 * @inheritdoc
582
	 */
583
	public function deleteFromSelf(IShare $share, $recipient) {
584
		// nothing to do here. Technically deleteFromSelf in the context of federated
585
		// shares is a umount of a external storage. This is handled here
586
		// apps/files_sharing/lib/external/manager.php
587
		// TODO move this code over to this app
588
		return;
589
	}
590
591
592 View Code Duplication
	public function getSharesInFolder($userId, Folder $node, $reshares) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
593
		$qb = $this->dbConnection->getQueryBuilder();
594
		$qb->select('*')
595
			->from('share', 's')
596
			->andWhere($qb->expr()->orX(
597
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
598
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
599
			))
600
			->andWhere(
601
				$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE))
602
			);
603
604
		/**
605
		 * Reshares for this user are shares where they are the owner.
606
		 */
607
		if ($reshares === false) {
608
			$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
609
		} else {
610
			$qb->andWhere(
611
				$qb->expr()->orX(
612
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
613
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
614
				)
615
			);
616
		}
617
618
		$qb->innerJoin('s', 'filecache' ,'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
619
		$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
620
621
		$qb->orderBy('id');
622
623
		$cursor = $qb->execute();
624
		$shares = [];
625
		while ($data = $cursor->fetch()) {
626
			$shares[$data['fileid']][] = $this->createShareObject($data);
627
		}
628
		$cursor->closeCursor();
629
630
		return $shares;
631
	}
632
633
	/**
634
	 * @inheritdoc
635
	 */
636 View Code Duplication
	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
637
		$qb = $this->dbConnection->getQueryBuilder();
638
		$qb->select('*')
639
			->from('share');
640
641
		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
642
643
		/**
644
		 * Reshares for this user are shares where they are the owner.
645
		 */
646
		if ($reshares === false) {
647
			//Special case for old shares created via the web UI
648
			$or1 = $qb->expr()->andX(
649
				$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
650
				$qb->expr()->isNull('uid_initiator')
651
			);
652
653
			$qb->andWhere(
654
				$qb->expr()->orX(
655
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
656
					$or1
657
				)
658
			);
659
		} else {
660
			$qb->andWhere(
661
				$qb->expr()->orX(
662
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
663
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
664
				)
665
			);
666
		}
667
668
		if ($node !== null) {
669
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
670
		}
671
672
		if ($limit !== -1) {
673
			$qb->setMaxResults($limit);
674
		}
675
676
		$qb->setFirstResult($offset);
677
		$qb->orderBy('id');
678
679
		$cursor = $qb->execute();
680
		$shares = [];
681
		while($data = $cursor->fetch()) {
682
			$shares[] = $this->createShareObject($data);
683
		}
684
		$cursor->closeCursor();
685
686
		return $shares;
687
	}
688
689
	/**
690
	 * @inheritdoc
691
	 */
692 View Code Duplication
	public function getShareById($id, $recipientId = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
693
		$qb = $this->dbConnection->getQueryBuilder();
694
695
		$qb->select('*')
696
			->from('share')
697
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
698
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
699
700
		$cursor = $qb->execute();
701
		$data = $cursor->fetch();
702
		$cursor->closeCursor();
703
704
		if ($data === false) {
705
			throw new ShareNotFound();
706
		}
707
708
		try {
709
			$share = $this->createShareObject($data);
710
		} catch (InvalidShare $e) {
711
			throw new ShareNotFound();
712
		}
713
714
		return $share;
715
	}
716
717
	/**
718
	 * Get shares for a given path
719
	 *
720
	 * @param \OCP\Files\Node $path
721
	 * @return IShare[]
722
	 */
723 View Code Duplication
	public function getSharesByPath(Node $path) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
724
		$qb = $this->dbConnection->getQueryBuilder();
725
726
		$cursor = $qb->select('*')
727
			->from('share')
728
			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
729
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
730
			->execute();
731
732
		$shares = [];
733
		while($data = $cursor->fetch()) {
734
			$shares[] = $this->createShareObject($data);
735
		}
736
		$cursor->closeCursor();
737
738
		return $shares;
739
	}
740
741
	/**
742
	 * @inheritdoc
743
	 */
744 View Code Duplication
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
745
		/** @var IShare[] $shares */
746
		$shares = [];
747
748
		//Get shares directly with this user
749
		$qb = $this->dbConnection->getQueryBuilder();
750
		$qb->select('*')
751
			->from('share');
752
753
		// Order by id
754
		$qb->orderBy('id');
755
756
		// Set limit and offset
757
		if ($limit !== -1) {
758
			$qb->setMaxResults($limit);
759
		}
760
		$qb->setFirstResult($offset);
761
762
		$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)));
763
		$qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
764
765
		// Filter by node if provided
766
		if ($node !== null) {
767
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
768
		}
769
770
		$cursor = $qb->execute();
771
772
		while($data = $cursor->fetch()) {
773
			$shares[] = $this->createShareObject($data);
774
		}
775
		$cursor->closeCursor();
776
777
778
		return $shares;
779
	}
780
781
	/**
782
	 * Get a share by token
783
	 *
784
	 * @param string $token
785
	 * @return IShare
786
	 * @throws ShareNotFound
787
	 */
788 View Code Duplication
	public function getShareByToken($token) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
789
		$qb = $this->dbConnection->getQueryBuilder();
790
791
		$cursor = $qb->select('*')
792
			->from('share')
793
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_REMOTE)))
794
			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
795
			->execute();
796
797
		$data = $cursor->fetch();
798
799
		if ($data === false) {
800
			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
801
		}
802
803
		try {
804
			$share = $this->createShareObject($data);
805
		} catch (InvalidShare $e) {
806
			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
807
		}
808
809
		return $share;
810
	}
811
812
	/**
813
	 * get database row of a give share
814
	 *
815
	 * @param $id
816
	 * @return array
817
	 * @throws ShareNotFound
818
	 */
819 View Code Duplication
	private function getRawShare($id) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
820
821
		// Now fetch the inserted share and create a complete share object
822
		$qb = $this->dbConnection->getQueryBuilder();
823
		$qb->select('*')
824
			->from('share')
825
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
826
827
		$cursor = $qb->execute();
828
		$data = $cursor->fetch();
829
		$cursor->closeCursor();
830
831
		if ($data === false) {
832
			throw new ShareNotFound;
833
		}
834
835
		return $data;
836
	}
837
838
	/**
839
	 * Create a share object from an database row
840
	 *
841
	 * @param array $data
842
	 * @return IShare
843
	 * @throws InvalidShare
844
	 * @throws ShareNotFound
845
	 */
846
	private function createShareObject($data) {
847
848
		$share = new Share($this->rootFolder, $this->userManager);
849
		$share->setId((int)$data['id'])
850
			->setShareType((int)$data['share_type'])
851
			->setPermissions((int)$data['permissions'])
852
			->setTarget($data['file_target'])
853
			->setMailSend((bool)$data['mail_send'])
854
			->setToken($data['token']);
855
856
		$shareTime = new \DateTime();
857
		$shareTime->setTimestamp((int)$data['stime']);
858
		$share->setShareTime($shareTime);
859
		$share->setSharedWith($data['share_with']);
860
861
		if ($data['uid_initiator'] !== null) {
862
			$share->setShareOwner($data['uid_owner']);
863
			$share->setSharedBy($data['uid_initiator']);
864
		} else {
865
			//OLD SHARE
866
			$share->setSharedBy($data['uid_owner']);
867
			$path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
868
869
			$owner = $path->getOwner();
870
			$share->setShareOwner($owner->getUID());
871
		}
872
873
		$share->setNodeId((int)$data['file_source']);
874
		$share->setNodeType($data['item_type']);
875
876
		$share->setProviderId($this->identifier());
877
878
		return $share;
879
	}
880
881
	/**
882
	 * Get the node with file $id for $user
883
	 *
884
	 * @param string $userId
885
	 * @param int $id
886
	 * @return \OCP\Files\File|\OCP\Files\Folder
887
	 * @throws InvalidShare
888
	 */
889 View Code Duplication
	private function getNode($userId, $id) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
890
		try {
891
			$userFolder = $this->rootFolder->getUserFolder($userId);
892
		} catch (NotFoundException $e) {
893
			throw new InvalidShare();
894
		}
895
896
		$nodes = $userFolder->getById($id);
897
898
		if (empty($nodes)) {
899
			throw new InvalidShare();
900
		}
901
902
		return $nodes[0];
903
	}
904
905
	/**
906
	 * A user is deleted from the system
907
	 * So clean up the relevant shares.
908
	 *
909
	 * @param string $uid
910
	 * @param int $shareType
911
	 */
912 View Code Duplication
	public function userDeleted($uid, $shareType) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
913
		//TODO: probabaly a good idea to send unshare info to remote servers
914
915
		$qb = $this->dbConnection->getQueryBuilder();
916
917
		$qb->delete('share')
918
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE)))
919
			->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
920
			->execute();
921
	}
922
923
	/**
924
	 * This provider does not handle groups
925
	 *
926
	 * @param string $gid
927
	 */
928
	public function groupDeleted($gid) {
929
		// We don't handle groups here
930
		return;
931
	}
932
933
	/**
934
	 * This provider does not handle groups
935
	 *
936
	 * @param string $uid
937
	 * @param string $gid
938
	 */
939
	public function userDeletedFromGroup($uid, $gid) {
940
		// We don't handle groups here
941
		return;
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() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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