Passed
Push — master ( ef6806...fd475d )
by Roeland
09:13 queued 10s
created

FederatedShareProvider::getShareById()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 15
nc 3
nop 2
dl 0
loc 23
rs 9.7666
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bjoern Schiessle <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 * @author Roeland Jago Douma <[email protected]>
12
 * @author Thomas Müller <[email protected]>
13
 *
14
 * @license AGPL-3.0
15
 *
16
 * This code is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License, version 3,
18
 * as published by the Free Software Foundation.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License, version 3,
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
27
 *
28
 */
29
30
namespace OCA\FederatedFileSharing;
31
32
use OC\Share20\Share;
33
use OCP\Federation\ICloudFederationProviderManager;
34
use OCP\Federation\ICloudIdManager;
35
use OCP\DB\QueryBuilder\IQueryBuilder;
36
use OCP\Files\Folder;
37
use OCP\Files\IRootFolder;
38
use OCP\IConfig;
39
use OCP\IL10N;
40
use OCP\ILogger;
41
use OCP\IUserManager;
42
use OCP\Share\Exceptions\GenericShareException;
43
use OCP\Share\IShare;
44
use OCP\Share\IShareProvider;
45
use OC\Share20\Exception\InvalidShare;
46
use OCP\Share\Exceptions\ShareNotFound;
47
use OCP\Files\NotFoundException;
48
use OCP\IDBConnection;
49
use OCP\Files\Node;
50
51
/**
52
 * Class FederatedShareProvider
53
 *
54
 * @package OCA\FederatedFileSharing
55
 */
56
class FederatedShareProvider implements IShareProvider {
57
58
	const SHARE_TYPE_REMOTE = 6;
59
60
	/** @var IDBConnection */
61
	private $dbConnection;
62
63
	/** @var AddressHandler */
64
	private $addressHandler;
65
66
	/** @var Notifications */
67
	private $notifications;
68
69
	/** @var TokenHandler */
70
	private $tokenHandler;
71
72
	/** @var IL10N */
73
	private $l;
74
75
	/** @var ILogger */
76
	private $logger;
77
78
	/** @var IRootFolder */
79
	private $rootFolder;
80
81
	/** @var IConfig */
82
	private $config;
83
84
	/** @var string */
85
	private $externalShareTable = 'share_external';
86
87
	/** @var IUserManager */
88
	private $userManager;
89
90
	/** @var ICloudIdManager */
91
	private $cloudIdManager;
92
93
	/** @var \OCP\GlobalScale\IConfig */
94
	private $gsConfig;
95
96
	/** @var ICloudFederationProviderManager */
97
	private $cloudFederationProviderManager;
98
99
	/** @var array list of supported share types */
100
	private $supportedShareType = [\OCP\Share::SHARE_TYPE_REMOTE_GROUP, \OCP\Share::SHARE_TYPE_REMOTE];
101
102
	/**
103
	 * DefaultShareProvider constructor.
104
	 *
105
	 * @param IDBConnection $connection
106
	 * @param AddressHandler $addressHandler
107
	 * @param Notifications $notifications
108
	 * @param TokenHandler $tokenHandler
109
	 * @param IL10N $l10n
110
	 * @param ILogger $logger
111
	 * @param IRootFolder $rootFolder
112
	 * @param IConfig $config
113
	 * @param IUserManager $userManager
114
	 * @param ICloudIdManager $cloudIdManager
115
	 * @param \OCP\GlobalScale\IConfig $globalScaleConfig
116
	 * @param ICloudFederationProviderManager $cloudFederationProviderManager
117
	 */
118
	public function __construct(
119
			IDBConnection $connection,
120
			AddressHandler $addressHandler,
121
			Notifications $notifications,
122
			TokenHandler $tokenHandler,
123
			IL10N $l10n,
124
			ILogger $logger,
125
			IRootFolder $rootFolder,
126
			IConfig $config,
127
			IUserManager $userManager,
128
			ICloudIdManager $cloudIdManager,
129
			\OCP\GlobalScale\IConfig $globalScaleConfig,
130
			ICloudFederationProviderManager $cloudFederationProviderManager
131
	) {
132
		$this->dbConnection = $connection;
133
		$this->addressHandler = $addressHandler;
134
		$this->notifications = $notifications;
135
		$this->tokenHandler = $tokenHandler;
136
		$this->l = $l10n;
137
		$this->logger = $logger;
138
		$this->rootFolder = $rootFolder;
139
		$this->config = $config;
140
		$this->userManager = $userManager;
141
		$this->cloudIdManager = $cloudIdManager;
142
		$this->gsConfig = $globalScaleConfig;
143
		$this->cloudFederationProviderManager = $cloudFederationProviderManager;
144
	}
145
146
	/**
147
	 * Return the identifier of this provider.
148
	 *
149
	 * @return string Containing only [a-zA-Z0-9]
150
	 */
151
	public function identifier() {
152
		return 'ocFederatedSharing';
153
	}
154
155
	/**
156
	 * Share a path
157
	 *
158
	 * @param IShare $share
159
	 * @return IShare The share object
160
	 * @throws ShareNotFound
161
	 * @throws \Exception
162
	 */
163
	public function create(IShare $share) {
164
165
		$shareWith = $share->getSharedWith();
166
		$itemSource = $share->getNodeId();
167
		$itemType = $share->getNodeType();
168
		$permissions = $share->getPermissions();
169
		$sharedBy = $share->getSharedBy();
170
		$shareType = $share->getShareType();
171
172
		if ($shareType === \OCP\Share::SHARE_TYPE_REMOTE_GROUP &&
0 ignored issues
show
Deprecated Code introduced by
The constant OC\Share\Constants::SHARE_TYPE_REMOTE_GROUP has been deprecated: 17.0.0 - use IShare::REMOTE_GROUP instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

172
		if ($shareType === /** @scrutinizer ignore-deprecated */ \OCP\Share::SHARE_TYPE_REMOTE_GROUP &&

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
173
			!$this->isOutgoingServer2serverGroupShareEnabled()
174
		) {
175
			$message = 'It is not allowed to send federated group shares from this server.';
176
			$message_t = $this->l->t('It is not allowed to send federated group shares from this server.');
177
			$this->logger->debug($message, ['app' => 'Federated File Sharing']);
178
			throw new \Exception($message_t);
179
		}
180
181
		/*
182
		 * Check if file is not already shared with the remote user
183
		 */
184
		$alreadyShared = $this->getSharedWith($shareWith, \OCP\Share::SHARE_TYPE_REMOTE, $share->getNode(), 1, 0);
0 ignored issues
show
Deprecated Code introduced by
The constant OC\Share\Constants::SHARE_TYPE_REMOTE has been deprecated: 17.0.0 - use IShare::TYPE_REMOTE instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

184
		$alreadyShared = $this->getSharedWith($shareWith, /** @scrutinizer ignore-deprecated */ \OCP\Share::SHARE_TYPE_REMOTE, $share->getNode(), 1, 0);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
185
		$alreadySharedGroup = $this->getSharedWith($shareWith, \OCP\Share::SHARE_TYPE_REMOTE_GROUP, $share->getNode(), 1, 0);
0 ignored issues
show
Deprecated Code introduced by
The constant OC\Share\Constants::SHARE_TYPE_REMOTE_GROUP has been deprecated: 17.0.0 - use IShare::REMOTE_GROUP instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

185
		$alreadySharedGroup = $this->getSharedWith($shareWith, /** @scrutinizer ignore-deprecated */ \OCP\Share::SHARE_TYPE_REMOTE_GROUP, $share->getNode(), 1, 0);

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
186
		if (!empty($alreadyShared) || !empty($alreadySharedGroup)) {
187
			$message = 'Sharing %1$s failed, because this item is already shared with %2$s';
188
			$message_t = $this->l->t('Sharing %1$s failed, because this item is already shared with %2$s', array($share->getNode()->getName(), $shareWith));
189
			$this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
190
			throw new \Exception($message_t);
191
		}
192
193
194
		// don't allow federated shares if source and target server are the same
195
		$cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
196
		$currentServer = $this->addressHandler->generateRemoteURL();
197
		$currentUser = $sharedBy;
198
		if ($this->addressHandler->compareAddresses($cloudId->getUser(), $cloudId->getRemote(), $currentUser, $currentServer)) {
199
			$message = 'Not allowed to create a federated share with the same user.';
200
			$message_t = $this->l->t('Not allowed to create a federated share with the same user');
201
			$this->logger->debug($message, ['app' => 'Federated File Sharing']);
202
			throw new \Exception($message_t);
203
		}
204
205
206
		$share->setSharedWith($cloudId->getId());
207
208
		try {
209
			$remoteShare = $this->getShareFromExternalShareTable($share);
210
		} catch (ShareNotFound $e) {
211
			$remoteShare = null;
212
		}
213
214
		if ($remoteShare) {
215
			try {
216
				$ownerCloudId = $this->cloudIdManager->getCloudId($remoteShare['owner'], $remoteShare['remote']);
217
				$shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ownerCloudId->getId(), $permissions, 'tmp_token_' . time(), $shareType);
218
				$share->setId($shareId);
219
				list($token, $remoteId) = $this->askOwnerToReShare($shareWith, $share, $shareId);
220
				// remote share was create successfully if we get a valid token as return
221
				$send = is_string($token) && $token !== '';
222
			} catch (\Exception $e) {
223
				// fall back to old re-share behavior if the remote server
224
				// doesn't support flat re-shares (was introduced with Nextcloud 9.1)
225
				$this->removeShareFromTable($share);
226
				$shareId = $this->createFederatedShare($share);
227
			}
228
			if ($send) {
229
				$this->updateSuccessfulReshare($shareId, $token);
230
				$this->storeRemoteId($shareId, $remoteId);
231
			} else {
232
				$this->removeShareFromTable($share);
233
				$message_t = $this->l->t('File is already shared with %s', [$shareWith]);
234
				throw new \Exception($message_t);
235
			}
236
237
		} else {
238
			$shareId = $this->createFederatedShare($share);
239
		}
240
241
		$data = $this->getRawShare($shareId);
242
		return $this->createShareObject($data);
243
	}
244
245
	/**
246
	 * create federated share and inform the recipient
247
	 *
248
	 * @param IShare $share
249
	 * @return int
250
	 * @throws ShareNotFound
251
	 * @throws \Exception
252
	 */
253
	protected function createFederatedShare(IShare $share) {
254
		$token = $this->tokenHandler->generateToken();
255
		$shareId = $this->addShareToDB(
256
			$share->getNodeId(),
257
			$share->getNodeType(),
258
			$share->getSharedWith(),
259
			$share->getSharedBy(),
260
			$share->getShareOwner(),
261
			$share->getPermissions(),
262
			$token,
263
			$share->getShareType()
264
		);
265
266
		$failure = false;
267
268
		try {
269
			$sharedByFederatedId = $share->getSharedBy();
270
			if ($this->userManager->userExists($sharedByFederatedId)) {
271
				$cloudId = $this->cloudIdManager->getCloudId($sharedByFederatedId, $this->addressHandler->generateRemoteURL());
272
				$sharedByFederatedId = $cloudId->getId();
273
			}
274
			$ownerCloudId = $this->cloudIdManager->getCloudId($share->getShareOwner(), $this->addressHandler->generateRemoteURL());
275
			$send = $this->notifications->sendRemoteShare(
276
				$token,
277
				$share->getSharedWith(),
278
				$share->getNode()->getName(),
279
				$shareId,
280
				$share->getShareOwner(),
281
				$ownerCloudId->getId(),
282
				$share->getSharedBy(),
283
				$sharedByFederatedId,
284
				$share->getShareType()
285
			);
286
287
			if ($send === false) {
288
				$failure = true;
289
			}
290
		} catch (\Exception $e) {
291
			$this->logger->logException($e, [
292
				'message' => 'Failed to notify remote server of federated share, removing share.',
293
				'level' => ILogger::ERROR,
294
				'app' => 'federatedfilesharing',
295
			]);
296
			$failure = true;
297
		}
298
299
		if($failure) {
300
			$this->removeShareFromTableById($shareId);
301
			$message_t = $this->l->t('Sharing %1$s failed, could not find %2$s, maybe the server is currently unreachable or uses a self-signed certificate.',
302
				[$share->getNode()->getName(), $share->getSharedWith()]);
303
			throw new \Exception($message_t);
304
		}
305
306
		return $shareId;
307
308
	}
309
310
	/**
311
	 * @param string $shareWith
312
	 * @param IShare $share
313
	 * @param string $shareId internal share Id
314
	 * @return array
315
	 * @throws \Exception
316
	 */
317
	protected function askOwnerToReShare($shareWith, IShare $share, $shareId) {
318
319
		$remoteShare = $this->getShareFromExternalShareTable($share);
320
		$token = $remoteShare['share_token'];
321
		$remoteId = $remoteShare['remote_id'];
322
		$remote = $remoteShare['remote'];
323
324
		list($token, $remoteId) = $this->notifications->requestReShare(
325
			$token,
326
			$remoteId,
327
			$shareId,
0 ignored issues
show
Bug introduced by
$shareId of type string is incompatible with the type integer expected by parameter $shareId of OCA\FederatedFileSharing...tions::requestReShare(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

327
			/** @scrutinizer ignore-type */ $shareId,
Loading history...
328
			$remote,
329
			$shareWith,
330
			$share->getPermissions(),
331
			$share->getNode()->getName()
332
		);
333
334
		return [$token, $remoteId];
335
	}
336
337
	/**
338
	 * get federated share from the share_external table but exclude mounted link shares
339
	 *
340
	 * @param IShare $share
341
	 * @return array
342
	 * @throws ShareNotFound
343
	 */
344
	protected function getShareFromExternalShareTable(IShare $share) {
345
		$query = $this->dbConnection->getQueryBuilder();
346
		$query->select('*')->from($this->externalShareTable)
347
			->where($query->expr()->eq('user', $query->createNamedParameter($share->getShareOwner())))
348
			->andWhere($query->expr()->eq('mountpoint', $query->createNamedParameter($share->getTarget())));
349
		$result = $query->execute()->fetchAll();
350
351
		if (isset($result[0]) && (int)$result[0]['remote_id'] > 0) {
352
			return $result[0];
353
		}
354
355
		throw new ShareNotFound('share not found in share_external table');
356
	}
357
358
	/**
359
	 * add share to the database and return the ID
360
	 *
361
	 * @param int $itemSource
362
	 * @param string $itemType
363
	 * @param string $shareWith
364
	 * @param string $sharedBy
365
	 * @param string $uidOwner
366
	 * @param int $permissions
367
	 * @param string $token
368
	 * @param int $shareType
369
	 * @return int
370
	 */
371
	private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $shareType) {
372
		$qb = $this->dbConnection->getQueryBuilder();
373
		$qb->insert('share')
374
			->setValue('share_type', $qb->createNamedParameter($shareType))
375
			->setValue('item_type', $qb->createNamedParameter($itemType))
376
			->setValue('item_source', $qb->createNamedParameter($itemSource))
377
			->setValue('file_source', $qb->createNamedParameter($itemSource))
378
			->setValue('share_with', $qb->createNamedParameter($shareWith))
379
			->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
380
			->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
381
			->setValue('permissions', $qb->createNamedParameter($permissions))
382
			->setValue('token', $qb->createNamedParameter($token))
383
			->setValue('stime', $qb->createNamedParameter(time()));
384
385
		/*
386
		 * Added to fix https://github.com/owncloud/core/issues/22215
387
		 * Can be removed once we get rid of ajax/share.php
388
		 */
389
		$qb->setValue('file_target', $qb->createNamedParameter(''));
390
391
		$qb->execute();
392
		$id = $qb->getLastInsertId();
393
394
		return (int)$id;
395
	}
396
397
	/**
398
	 * Update a share
399
	 *
400
	 * @param IShare $share
401
	 * @return IShare The share object
402
	 */
403
	public function update(IShare $share) {
404
		/*
405
		 * We allow updating the permissions of federated shares
406
		 */
407
		$qb = $this->dbConnection->getQueryBuilder();
408
			$qb->update('share')
409
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
410
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
411
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
412
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
413
				->execute();
414
415
		// send the updated permission to the owner/initiator, if they are not the same
416
		if ($share->getShareOwner() !== $share->getSharedBy()) {
417
			$this->sendPermissionUpdate($share);
418
		}
419
420
		return $share;
421
	}
422
423
	/**
424
	 * send the updated permission to the owner/initiator, if they are not the same
425
	 *
426
	 * @param IShare $share
427
	 * @throws ShareNotFound
428
	 * @throws \OC\HintException
429
	 */
430
	protected function sendPermissionUpdate(IShare $share) {
431
		$remoteId = $this->getRemoteId($share);
432
		// if the local user is the owner we send the permission change to the initiator
433
		if ($this->userManager->userExists($share->getShareOwner())) {
434
			list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
435
		} else { // ... if not we send the permission change to the owner
436
			list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
437
		}
438
		$this->notifications->sendPermissionChange($remote, $remoteId, $share->getToken(), $share->getPermissions());
439
	}
440
441
442
	/**
443
	 * update successful reShare with the correct token
444
	 *
445
	 * @param int $shareId
446
	 * @param string $token
447
	 */
448
	protected function updateSuccessfulReShare($shareId, $token) {
449
		$query = $this->dbConnection->getQueryBuilder();
450
		$query->update('share')
451
			->where($query->expr()->eq('id', $query->createNamedParameter($shareId)))
452
			->set('token', $query->createNamedParameter($token))
453
			->execute();
454
	}
455
456
	/**
457
	 * store remote ID in federated reShare table
458
	 *
459
	 * @param $shareId
460
	 * @param $remoteId
461
	 */
462
	public function storeRemoteId($shareId, $remoteId) {
463
		$query = $this->dbConnection->getQueryBuilder();
464
		$query->insert('federated_reshares')
465
			->values(
466
				[
467
					'share_id' =>  $query->createNamedParameter($shareId),
468
					'remote_id' => $query->createNamedParameter($remoteId),
469
				]
470
			);
471
		$query->execute();
472
	}
473
474
	/**
475
	 * get share ID on remote server for federated re-shares
476
	 *
477
	 * @param IShare $share
478
	 * @return int
479
	 * @throws ShareNotFound
480
	 */
481
	public function getRemoteId(IShare $share) {
482
		$query = $this->dbConnection->getQueryBuilder();
483
		$query->select('remote_id')->from('federated_reshares')
484
			->where($query->expr()->eq('share_id', $query->createNamedParameter((int)$share->getId())));
485
		$data = $query->execute()->fetch();
486
487
		if (!is_array($data) || !isset($data['remote_id'])) {
488
			throw new ShareNotFound();
489
		}
490
491
		return (int)$data['remote_id'];
492
	}
493
494
	/**
495
	 * @inheritdoc
496
	 */
497
	public function move(IShare $share, $recipient) {
498
		/*
499
		 * This function does nothing yet as it is just for outgoing
500
		 * federated shares.
501
		 */
502
		return $share;
503
	}
504
505
	/**
506
	 * Get all children of this share
507
	 *
508
	 * @param IShare $parent
509
	 * @return IShare[]
510
	 */
511
	public function getChildren(IShare $parent) {
512
		$children = [];
513
514
		$qb = $this->dbConnection->getQueryBuilder();
515
		$qb->select('*')
516
			->from('share')
517
			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
518
			->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)))
519
			->orderBy('id');
520
521
		$cursor = $qb->execute();
522
		while($data = $cursor->fetch()) {
523
			$children[] = $this->createShareObject($data);
524
		}
525
		$cursor->closeCursor();
526
527
		return $children;
528
	}
529
530
	/**
531
	 * Delete a share (owner unShares the file)
532
	 *
533
	 * @param IShare $share
534
	 * @throws ShareNotFound
535
	 * @throws \OC\HintException
536
	 */
537
	public function delete(IShare $share) {
538
539
		list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedWith());
540
541
		// if the local user is the owner we can send the unShare request directly...
542
		if ($this->userManager->userExists($share->getShareOwner())) {
543
			$this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken());
0 ignored issues
show
Bug introduced by
$share->getId() of type string is incompatible with the type integer expected by parameter $id of OCA\FederatedFileSharing...ns::sendRemoteUnShare(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

543
			$this->notifications->sendRemoteUnShare($remote, /** @scrutinizer ignore-type */ $share->getId(), $share->getToken());
Loading history...
544
			$this->revokeShare($share, true);
545
		} else { // ... if not we need to correct ID for the unShare request
546
			$remoteId = $this->getRemoteId($share);
547
			$this->notifications->sendRemoteUnShare($remote, $remoteId, $share->getToken());
548
			$this->revokeShare($share, false);
549
		}
550
551
		// only remove the share when all messages are send to not lose information
552
		// about the share to early
553
		$this->removeShareFromTable($share);
554
	}
555
556
	/**
557
	 * in case of a re-share we need to send the other use (initiator or owner)
558
	 * a message that the file was unshared
559
	 *
560
	 * @param IShare $share
561
	 * @param bool $isOwner the user can either be the owner or the user who re-sahred it
562
	 * @throws ShareNotFound
563
	 * @throws \OC\HintException
564
	 */
565
	protected function revokeShare($share, $isOwner) {
566
		if ($this->userManager->userExists($share->getShareOwner() && $this->userManager->userExists($share->getSharedBy()))) {
567
			// If both the owner and the initiator of the share are local users we don't have to notify anybody else
568
			return;
569
		}
570
571
		// also send a unShare request to the initiator, if this is a different user than the owner
572
		if ($share->getShareOwner() !== $share->getSharedBy()) {
573
			if ($isOwner) {
574
				list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
575
			} else {
576
				list(, $remote) = $this->addressHandler->splitUserRemote($share->getShareOwner());
577
			}
578
			$remoteId = $this->getRemoteId($share);
579
			$this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken());
580
		}
581
	}
582
583
	/**
584
	 * remove share from table
585
	 *
586
	 * @param IShare $share
587
	 */
588
	public function removeShareFromTable(IShare $share) {
589
		$this->removeShareFromTableById($share->getId());
590
	}
591
592
	/**
593
	 * remove share from table
594
	 *
595
	 * @param string $shareId
596
	 */
597
	private function removeShareFromTableById($shareId) {
598
		$qb = $this->dbConnection->getQueryBuilder();
599
		$qb->delete('share')
600
			->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)));
601
		$qb->execute();
602
603
		$qb->delete('federated_reshares')
604
			->where($qb->expr()->eq('share_id', $qb->createNamedParameter($shareId)));
605
		$qb->execute();
606
	}
607
608
	/**
609
	 * @inheritdoc
610
	 */
611
	public function deleteFromSelf(IShare $share, $recipient) {
612
		// nothing to do here. Technically deleteFromSelf in the context of federated
613
		// shares is a umount of a external storage. This is handled here
614
		// apps/files_sharing/lib/external/manager.php
615
		// TODO move this code over to this app
616
	}
617
618
	public function restore(IShare $share, string $recipient): IShare {
619
		throw new GenericShareException('not implemented');
620
	}
621
622
623
	public function getSharesInFolder($userId, Folder $node, $reshares) {
624
		$qb = $this->dbConnection->getQueryBuilder();
625
		$qb->select('*')
626
			->from('share', 's')
627
			->andWhere($qb->expr()->orX(
628
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
629
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
630
			))
631
			->andWhere(
632
				$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE))
0 ignored issues
show
Deprecated Code introduced by
The constant OC\Share\Constants::SHARE_TYPE_REMOTE has been deprecated: 17.0.0 - use IShare::TYPE_REMOTE instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

632
				$qb->expr()->eq('share_type', $qb->createNamedParameter(/** @scrutinizer ignore-deprecated */ \OCP\Share::SHARE_TYPE_REMOTE))

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
633
			);
634
635
		/**
636
		 * Reshares for this user are shares where they are the owner.
637
		 */
638
		if ($reshares === false) {
639
			$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
640
		} else {
641
			$qb->andWhere(
642
				$qb->expr()->orX(
643
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
644
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
645
				)
646
			);
647
		}
648
649
		$qb->innerJoin('s', 'filecache' ,'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
650
		$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
651
652
		$qb->orderBy('id');
653
654
		$cursor = $qb->execute();
655
		$shares = [];
656
		while ($data = $cursor->fetch()) {
657
			$shares[$data['fileid']][] = $this->createShareObject($data);
658
		}
659
		$cursor->closeCursor();
660
661
		return $shares;
662
	}
663
664
	/**
665
	 * @inheritdoc
666
	 */
667
	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
668
		$qb = $this->dbConnection->getQueryBuilder();
669
		$qb->select('*')
670
			->from('share');
671
672
		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter($shareType)));
673
674
		/**
675
		 * Reshares for this user are shares where they are the owner.
676
		 */
677
		if ($reshares === false) {
678
			//Special case for old shares created via the web UI
679
			$or1 = $qb->expr()->andX(
680
				$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
681
				$qb->expr()->isNull('uid_initiator')
682
			);
683
684
			$qb->andWhere(
685
				$qb->expr()->orX(
686
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
687
					$or1
688
				)
689
			);
690
		} else {
691
			$qb->andWhere(
692
				$qb->expr()->orX(
693
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
694
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
695
				)
696
			);
697
		}
698
699
		if ($node !== null) {
700
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
701
		}
702
703
		if ($limit !== -1) {
704
			$qb->setMaxResults($limit);
705
		}
706
707
		$qb->setFirstResult($offset);
708
		$qb->orderBy('id');
709
710
		$cursor = $qb->execute();
711
		$shares = [];
712
		while($data = $cursor->fetch()) {
713
			$shares[] = $this->createShareObject($data);
714
		}
715
		$cursor->closeCursor();
716
717
		return $shares;
718
	}
719
720
	/**
721
	 * @inheritdoc
722
	 */
723
	public function getShareById($id, $recipientId = null) {
724
		$qb = $this->dbConnection->getQueryBuilder();
725
726
		$qb->select('*')
727
			->from('share')
728
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
729
			->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)));
730
731
		$cursor = $qb->execute();
732
		$data = $cursor->fetch();
733
		$cursor->closeCursor();
734
735
		if ($data === false) {
736
			throw new ShareNotFound('Can not find share with ID: ' . $id);
737
		}
738
739
		try {
740
			$share = $this->createShareObject($data);
741
		} catch (InvalidShare $e) {
742
			throw new ShareNotFound();
743
		}
744
745
		return $share;
746
	}
747
748
	/**
749
	 * Get shares for a given path
750
	 *
751
	 * @param \OCP\Files\Node $path
752
	 * @return IShare[]
753
	 */
754
	public function getSharesByPath(Node $path) {
755
		$qb = $this->dbConnection->getQueryBuilder();
756
757
		// get federated user shares
758
		$cursor = $qb->select('*')
759
			->from('share')
760
			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
761
			->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)))
762
			->execute();
763
764
		$shares = [];
765
		while($data = $cursor->fetch()) {
766
			$shares[] = $this->createShareObject($data);
767
		}
768
		$cursor->closeCursor();
769
770
		return $shares;
771
	}
772
773
	/**
774
	 * @inheritdoc
775
	 */
776
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
777
		/** @var IShare[] $shares */
778
		$shares = [];
779
780
		//Get shares directly with this user
781
		$qb = $this->dbConnection->getQueryBuilder();
782
		$qb->select('*')
783
			->from('share');
784
785
		// Order by id
786
		$qb->orderBy('id');
787
788
		// Set limit and offset
789
		if ($limit !== -1) {
790
			$qb->setMaxResults($limit);
791
		}
792
		$qb->setFirstResult($offset);
793
794
		$qb->where($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)));
795
		$qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
796
797
		// Filter by node if provided
798
		if ($node !== null) {
799
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
800
		}
801
802
		$cursor = $qb->execute();
803
804
		while($data = $cursor->fetch()) {
805
			$shares[] = $this->createShareObject($data);
806
		}
807
		$cursor->closeCursor();
808
809
810
		return $shares;
811
	}
812
813
	/**
814
	 * Get a share by token
815
	 *
816
	 * @param string $token
817
	 * @return IShare
818
	 * @throws ShareNotFound
819
	 */
820
	public function getShareByToken($token) {
821
		$qb = $this->dbConnection->getQueryBuilder();
822
823
		$cursor = $qb->select('*')
824
			->from('share')
825
			->where($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)))
826
			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
827
			->execute();
828
829
		$data = $cursor->fetch();
830
831
		if ($data === false) {
832
			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
833
		}
834
835
		try {
836
			$share = $this->createShareObject($data);
837
		} catch (InvalidShare $e) {
838
			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
839
		}
840
841
		return $share;
842
	}
843
844
	/**
845
	 * get database row of a give share
846
	 *
847
	 * @param $id
848
	 * @return array
849
	 * @throws ShareNotFound
850
	 */
851
	private function getRawShare($id) {
852
853
		// Now fetch the inserted share and create a complete share object
854
		$qb = $this->dbConnection->getQueryBuilder();
855
		$qb->select('*')
856
			->from('share')
857
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
858
859
		$cursor = $qb->execute();
860
		$data = $cursor->fetch();
861
		$cursor->closeCursor();
862
863
		if ($data === false) {
864
			throw new ShareNotFound;
865
		}
866
867
		return $data;
868
	}
869
870
	/**
871
	 * Create a share object from an database row
872
	 *
873
	 * @param array $data
874
	 * @return IShare
875
	 * @throws InvalidShare
876
	 * @throws ShareNotFound
877
	 */
878
	private function createShareObject($data) {
879
880
		$share = new Share($this->rootFolder, $this->userManager);
881
		$share->setId((int)$data['id'])
882
			->setShareType((int)$data['share_type'])
883
			->setPermissions((int)$data['permissions'])
884
			->setTarget($data['file_target'])
885
			->setMailSend((bool)$data['mail_send'])
886
			->setToken($data['token']);
887
888
		$shareTime = new \DateTime();
889
		$shareTime->setTimestamp((int)$data['stime']);
890
		$share->setShareTime($shareTime);
891
		$share->setSharedWith($data['share_with']);
892
893
		if ($data['uid_initiator'] !== null) {
894
			$share->setShareOwner($data['uid_owner']);
895
			$share->setSharedBy($data['uid_initiator']);
896
		} else {
897
			//OLD SHARE
898
			$share->setSharedBy($data['uid_owner']);
899
			$path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
900
901
			$owner = $path->getOwner();
902
			$share->setShareOwner($owner->getUID());
903
		}
904
905
		$share->setNodeId((int)$data['file_source']);
906
		$share->setNodeType($data['item_type']);
907
908
		$share->setProviderId($this->identifier());
909
910
		return $share;
911
	}
912
913
	/**
914
	 * Get the node with file $id for $user
915
	 *
916
	 * @param string $userId
917
	 * @param int $id
918
	 * @return \OCP\Files\File|\OCP\Files\Folder
919
	 * @throws InvalidShare
920
	 */
921
	private function getNode($userId, $id) {
922
		try {
923
			$userFolder = $this->rootFolder->getUserFolder($userId);
924
		} catch (NotFoundException $e) {
925
			throw new InvalidShare();
926
		}
927
928
		$nodes = $userFolder->getById($id);
929
930
		if (empty($nodes)) {
931
			throw new InvalidShare();
932
		}
933
934
		return $nodes[0];
935
	}
936
937
	/**
938
	 * A user is deleted from the system
939
	 * So clean up the relevant shares.
940
	 *
941
	 * @param string $uid
942
	 * @param int $shareType
943
	 */
944
	public function userDeleted($uid, $shareType) {
945
		//TODO: probabaly a good idea to send unshare info to remote servers
946
947
		$qb = $this->dbConnection->getQueryBuilder();
948
949
		$qb->delete('share')
950
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE)))
0 ignored issues
show
Deprecated Code introduced by
The constant OC\Share\Constants::SHARE_TYPE_REMOTE has been deprecated: 17.0.0 - use IShare::TYPE_REMOTE instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

950
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(/** @scrutinizer ignore-deprecated */ \OCP\Share::SHARE_TYPE_REMOTE)))

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
951
			->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
952
			->execute();
953
	}
954
955
	/**
956
	 * This provider does not handle groups
957
	 *
958
	 * @param string $gid
959
	 */
960
	public function groupDeleted($gid) {
961
		// We don't handle groups here
962
	}
963
964
	/**
965
	 * This provider does not handle groups
966
	 *
967
	 * @param string $uid
968
	 * @param string $gid
969
	 */
970
	public function userDeletedFromGroup($uid, $gid) {
971
		// We don't handle groups here
972
	}
973
974
	/**
975
	 * check if users from other Nextcloud instances are allowed to mount public links share by this instance
976
	 *
977
	 * @return bool
978
	 */
979
	public function isOutgoingServer2serverShareEnabled() {
980
		if ($this->gsConfig->onlyInternalFederation()) {
981
			return false;
982
		}
983
		$result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes');
984
		return ($result === 'yes');
985
	}
986
987
	/**
988
	 * check if users are allowed to mount public links from other Nextclouds
989
	 *
990
	 * @return bool
991
	 */
992
	public function isIncomingServer2serverShareEnabled() {
993
		if ($this->gsConfig->onlyInternalFederation()) {
994
			return false;
995
		}
996
		$result = $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes');
997
		return ($result === 'yes');
998
	}
999
1000
1001
	/**
1002
	 * check if users from other Nextcloud instances are allowed to send federated group shares
1003
	 *
1004
	 * @return bool
1005
	 */
1006
	public function isOutgoingServer2serverGroupShareEnabled() {
1007
		if ($this->gsConfig->onlyInternalFederation()) {
1008
			return false;
1009
		}
1010
		$result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no');
1011
		return ($result === 'yes');
1012
	}
1013
1014
	/**
1015
	 * check if users are allowed to receive federated group shares
1016
	 *
1017
	 * @return bool
1018
	 */
1019
	public function isIncomingServer2serverGroupShareEnabled() {
1020
		if ($this->gsConfig->onlyInternalFederation()) {
1021
			return false;
1022
		}
1023
		$result = $this->config->getAppValue('files_sharing', 'incoming_server2server_group_share_enabled', 'no');
1024
		return ($result === 'yes');
1025
	}
1026
1027
	/**
1028
	 * check if federated group sharing is supported, therefore the OCM API need to be enabled
1029
	 *
1030
	 * @return bool
1031
	 */
1032
	public function isFederatedGroupSharingSupported() {
1033
		return $this->cloudFederationProviderManager->isReady();
1034
	}
1035
1036
	/**
1037
	 * Check if querying sharees on the lookup server is enabled
1038
	 *
1039
	 * @return bool
1040
	 */
1041
	public function isLookupServerQueriesEnabled() {
1042
		// in a global scale setup we should always query the lookup server
1043
		if ($this->gsConfig->isGlobalScaleEnabled()) {
1044
			return true;
1045
		}
1046
		$result = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no');
1047
		return ($result === 'yes');
1048
	}
1049
1050
1051
	/**
1052
	 * Check if it is allowed to publish user specific data to the lookup server
1053
	 *
1054
	 * @return bool
1055
	 */
1056
	public function isLookupServerUploadEnabled() {
1057
		// in a global scale setup the admin is responsible to keep the lookup server up-to-date
1058
		if ($this->gsConfig->isGlobalScaleEnabled()) {
1059
			return false;
1060
		}
1061
		$result = $this->config->getAppValue('files_sharing', 'lookupServerUploadEnabled', 'yes');
1062
		return ($result === 'yes');
1063
	}
1064
1065
	/**
1066
	 * @inheritdoc
1067
	 */
1068
	public function getAccessList($nodes, $currentAccess) {
1069
		$ids = [];
1070
		foreach ($nodes as $node) {
1071
			$ids[] = $node->getId();
1072
		}
1073
1074
		$qb = $this->dbConnection->getQueryBuilder();
1075
		$qb->select('share_with', 'token', 'file_source')
1076
			->from('share')
1077
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_REMOTE)))
0 ignored issues
show
Deprecated Code introduced by
The constant OC\Share\Constants::SHARE_TYPE_REMOTE has been deprecated: 17.0.0 - use IShare::TYPE_REMOTE instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1077
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(/** @scrutinizer ignore-deprecated */ \OCP\Share::SHARE_TYPE_REMOTE)))

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
1078
			->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1079
			->andWhere($qb->expr()->orX(
1080
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1081
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1082
			));
1083
		$cursor = $qb->execute();
1084
1085
		if ($currentAccess === false) {
1086
			$remote = $cursor->fetch() !== false;
1087
			$cursor->closeCursor();
1088
1089
			return ['remote' => $remote];
1090
		}
1091
1092
		$remote = [];
1093
		while ($row = $cursor->fetch()) {
1094
			$remote[$row['share_with']] = [
1095
				'node_id' => $row['file_source'],
1096
				'token' => $row['token'],
1097
			];
1098
		}
1099
		$cursor->closeCursor();
1100
1101
		return ['remote' => $remote];
1102
	}
1103
1104
	public function getAllShares(): iterable {
1105
		$qb = $this->dbConnection->getQueryBuilder();
1106
1107
		$qb->select('*')
1108
			->from('share')
1109
			->where(
1110
				$qb->expr()->orX(
1111
					$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_REMOTE)),
1112
					$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_REMOTE_GROUP))
1113
				)
1114
			);
1115
1116
		$cursor = $qb->execute();
1117
		while($data = $cursor->fetch()) {
1118
			try {
1119
				$share = $this->createShareObject($data);
1120
			} catch (InvalidShare $e) {
1121
				continue;
1122
			} catch (ShareNotFound $e) {
1123
				continue;
1124
			}
1125
1126
			yield $share;
1127
		}
1128
		$cursor->closeCursor();
1129
	}
1130
}
1131