FederatedShareProvider   F
last analyzed

Complexity

Total Complexity 100

Size/Duplication

Total Lines 1092
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 475
c 0
b 0
f 0
dl 0
loc 1092
rs 2
wmc 100

41 Methods

Rating   Name   Duplication   Size   Complexity  
A getRemoteId() 0 13 3
A revokeShare() 0 15 5
A update() 0 19 2
A getChildren() 0 17 2
B createFederatedShare() 0 55 5
A move() 0 6 1
A updateSuccessfulReShare() 0 6 1
A identifier() 0 2 1
A storeRemoteId() 0 10 1
A removeShareFromTable() 0 2 1
A __construct() 0 26 1
A addShareToDB() 0 23 1
A askOwnerToReShare() 0 17 1
A delete() 0 16 2
A sendPermissionUpdate() 0 9 2
A getShareFromExternalShareTable() 0 14 3
C create() 0 86 12
A isLookupServerUploadEnabled() 0 7 2
A getNode() 0 14 3
A getAllShares() 0 25 4
A userDeleted() 0 9 1
A isFederatedGroupSharingSupported() 0 2 1
A isOutgoingServer2serverGroupShareEnabled() 0 6 2
A getShareByToken() 0 22 3
A getAccessList() 0 34 4
A getSharesBy() 0 51 5
A isLookupServerQueriesEnabled() 0 7 2
A getSharesByPath() 0 17 2
A getShareById() 0 23 3
A deleteFromSelf() 0 1 1
A restore() 0 2 1
A getRawShare() 0 17 2
A isIncomingServer2serverGroupShareEnabled() 0 6 2
A isOutgoingServer2serverShareEnabled() 0 6 2
A createShareObject() 0 37 3
A groupDeleted() 0 1 1
A userDeletedFromGroup() 0 1 1
A getSharesInFolder() 0 44 4
A getSharedWith() 0 35 4
A isIncomingServer2serverShareEnabled() 0 6 2
A removeShareFromTableById() 0 11 1

How to fix   Complexity   

Complex Class

Complex classes like FederatedShareProvider often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FederatedShareProvider, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bjoern Schiessle <[email protected]>
7
 * @author Björn Schießle <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Georg Ehrke <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author Julius Härtl <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Maxence Lange <[email protected]>
14
 * @author Morris Jobke <[email protected]>
15
 * @author Robin Appelman <[email protected]>
16
 * @author Roeland Jago Douma <[email protected]>
17
 * @author Sergej Pupykin <[email protected]>
18
 * @author Stefan Weil <[email protected]>
19
 * @author Thomas Müller <[email protected]>
20
 * @author Valdnet <[email protected]>
21
 * @author Vincent Petry <[email protected]>
22
 *
23
 * @license AGPL-3.0
24
 *
25
 * This code is free software: you can redistribute it and/or modify
26
 * it under the terms of the GNU Affero General Public License, version 3,
27
 * as published by the Free Software Foundation.
28
 *
29
 * This program is distributed in the hope that it will be useful,
30
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32
 * GNU Affero General Public License for more details.
33
 *
34
 * You should have received a copy of the GNU Affero General Public License, version 3,
35
 * along with this program. If not, see <http://www.gnu.org/licenses/>
36
 *
37
 */
38
namespace OCA\FederatedFileSharing;
39
40
use OC\Share20\Exception\InvalidShare;
41
use OC\Share20\Share;
42
use OCP\Constants;
43
use OCP\DB\QueryBuilder\IQueryBuilder;
44
use OCP\Federation\ICloudFederationProviderManager;
45
use OCP\Federation\ICloudIdManager;
46
use OCP\Files\Folder;
47
use OCP\Files\IRootFolder;
48
use OCP\Files\Node;
49
use OCP\Files\NotFoundException;
50
use OCP\IConfig;
51
use OCP\IDBConnection;
52
use OCP\IL10N;
53
use OCP\ILogger;
54
use OCP\IUserManager;
55
use OCP\Share\Exceptions\GenericShareException;
56
use OCP\Share\Exceptions\ShareNotFound;
57
use OCP\Share\IShare;
58
use OCP\Share\IShareProvider;
59
60
/**
61
 * Class FederatedShareProvider
62
 *
63
 * @package OCA\FederatedFileSharing
64
 */
65
class FederatedShareProvider implements IShareProvider {
66
	public const SHARE_TYPE_REMOTE = 6;
67
68
	/** @var IDBConnection */
69
	private $dbConnection;
70
71
	/** @var AddressHandler */
72
	private $addressHandler;
73
74
	/** @var Notifications */
75
	private $notifications;
76
77
	/** @var TokenHandler */
78
	private $tokenHandler;
79
80
	/** @var IL10N */
81
	private $l;
82
83
	/** @var ILogger */
84
	private $logger;
85
86
	/** @var IRootFolder */
87
	private $rootFolder;
88
89
	/** @var IConfig */
90
	private $config;
91
92
	/** @var string */
93
	private $externalShareTable = 'share_external';
94
95
	/** @var IUserManager */
96
	private $userManager;
97
98
	/** @var ICloudIdManager */
99
	private $cloudIdManager;
100
101
	/** @var \OCP\GlobalScale\IConfig */
102
	private $gsConfig;
103
104
	/** @var ICloudFederationProviderManager */
105
	private $cloudFederationProviderManager;
106
107
	/** @var array list of supported share types */
108
	private $supportedShareType = [IShare::TYPE_REMOTE_GROUP, IShare::TYPE_REMOTE, IShare::TYPE_CIRCLE];
109
110
	/**
111
	 * DefaultShareProvider constructor.
112
	 *
113
	 * @param IDBConnection $connection
114
	 * @param AddressHandler $addressHandler
115
	 * @param Notifications $notifications
116
	 * @param TokenHandler $tokenHandler
117
	 * @param IL10N $l10n
118
	 * @param ILogger $logger
119
	 * @param IRootFolder $rootFolder
120
	 * @param IConfig $config
121
	 * @param IUserManager $userManager
122
	 * @param ICloudIdManager $cloudIdManager
123
	 * @param \OCP\GlobalScale\IConfig $globalScaleConfig
124
	 * @param ICloudFederationProviderManager $cloudFederationProviderManager
125
	 */
126
	public function __construct(
127
			IDBConnection $connection,
128
			AddressHandler $addressHandler,
129
			Notifications $notifications,
130
			TokenHandler $tokenHandler,
131
			IL10N $l10n,
132
			ILogger $logger,
133
			IRootFolder $rootFolder,
134
			IConfig $config,
135
			IUserManager $userManager,
136
			ICloudIdManager $cloudIdManager,
137
			\OCP\GlobalScale\IConfig $globalScaleConfig,
138
			ICloudFederationProviderManager $cloudFederationProviderManager
139
	) {
140
		$this->dbConnection = $connection;
141
		$this->addressHandler = $addressHandler;
142
		$this->notifications = $notifications;
143
		$this->tokenHandler = $tokenHandler;
144
		$this->l = $l10n;
145
		$this->logger = $logger;
146
		$this->rootFolder = $rootFolder;
147
		$this->config = $config;
148
		$this->userManager = $userManager;
149
		$this->cloudIdManager = $cloudIdManager;
150
		$this->gsConfig = $globalScaleConfig;
151
		$this->cloudFederationProviderManager = $cloudFederationProviderManager;
152
	}
153
154
	/**
155
	 * Return the identifier of this provider.
156
	 *
157
	 * @return string Containing only [a-zA-Z0-9]
158
	 */
159
	public function identifier() {
160
		return 'ocFederatedSharing';
161
	}
162
163
	/**
164
	 * Share a path
165
	 *
166
	 * @param IShare $share
167
	 * @return IShare The share object
168
	 * @throws ShareNotFound
169
	 * @throws \Exception
170
	 */
171
	public function create(IShare $share) {
172
		$shareWith = $share->getSharedWith();
173
		$itemSource = $share->getNodeId();
174
		$itemType = $share->getNodeType();
175
		$permissions = $share->getPermissions();
176
		$sharedBy = $share->getSharedBy();
177
		$shareType = $share->getShareType();
178
		$expirationDate = $share->getExpirationDate();
179
180
		if ($shareType === IShare::TYPE_REMOTE_GROUP &&
181
			!$this->isOutgoingServer2serverGroupShareEnabled()
182
		) {
183
			$message = 'It is not allowed to send federated group shares from this server.';
184
			$message_t = $this->l->t('It is not allowed to send federated group shares from this server.');
185
			$this->logger->debug($message, ['app' => 'Federated File Sharing']);
186
			throw new \Exception($message_t);
187
		}
188
189
		/*
190
		 * Check if file is not already shared with the remote user
191
		 */
192
		$alreadyShared = $this->getSharedWith($shareWith, IShare::TYPE_REMOTE, $share->getNode(), 1, 0);
193
		$alreadySharedGroup = $this->getSharedWith($shareWith, IShare::TYPE_REMOTE_GROUP, $share->getNode(), 1, 0);
194
		if (!empty($alreadyShared) || !empty($alreadySharedGroup)) {
195
			$message = 'Sharing %1$s failed, because this item is already shared with %2$s';
196
			$message_t = $this->l->t('Sharing %1$s failed, because this item is already shared with user %2$s', [$share->getNode()->getName(), $shareWith]);
197
			$this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
198
			throw new \Exception($message_t);
199
		}
200
201
202
		// don't allow federated shares if source and target server are the same
203
		$cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
204
		$currentServer = $this->addressHandler->generateRemoteURL();
205
		$currentUser = $sharedBy;
206
		if ($this->addressHandler->compareAddresses($cloudId->getUser(), $cloudId->getRemote(), $currentUser, $currentServer)) {
207
			$message = 'Not allowed to create a federated share with the same user.';
208
			$message_t = $this->l->t('Not allowed to create a federated share with the same user');
209
			$this->logger->debug($message, ['app' => 'Federated File Sharing']);
210
			throw new \Exception($message_t);
211
		}
212
213
		// Federated shares always have read permissions
214
		if (($share->getPermissions() & Constants::PERMISSION_READ) === 0) {
215
			$message = 'Federated shares require read permissions';
216
			$message_t = $this->l->t('Federated shares require read permissions');
217
			$this->logger->debug($message, ['app' => 'Federated File Sharing']);
218
			throw new \Exception($message_t);
219
		}
220
221
		$share->setSharedWith($cloudId->getId());
222
223
		try {
224
			$remoteShare = $this->getShareFromExternalShareTable($share);
225
		} catch (ShareNotFound $e) {
226
			$remoteShare = null;
227
		}
228
229
		if ($remoteShare) {
230
			try {
231
				$ownerCloudId = $this->cloudIdManager->getCloudId($remoteShare['owner'], $remoteShare['remote']);
232
				$shareId = $this->addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $ownerCloudId->getId(), $permissions, 'tmp_token_' . time(), $shareType, $expirationDate);
233
				$share->setId($shareId);
234
				[$token, $remoteId] = $this->askOwnerToReShare($shareWith, $share, $shareId);
235
				// remote share was create successfully if we get a valid token as return
236
				$send = is_string($token) && $token !== '';
237
			} catch (\Exception $e) {
238
				// fall back to old re-share behavior if the remote server
239
				// doesn't support flat re-shares (was introduced with Nextcloud 9.1)
240
				$this->removeShareFromTable($share);
241
				$shareId = $this->createFederatedShare($share);
242
			}
243
			if ($send) {
244
				$this->updateSuccessfulReshare($shareId, $token);
245
				$this->storeRemoteId($shareId, $remoteId);
246
			} else {
247
				$this->removeShareFromTable($share);
248
				$message_t = $this->l->t('File is already shared with %s', [$shareWith]);
249
				throw new \Exception($message_t);
250
			}
251
		} else {
252
			$shareId = $this->createFederatedShare($share);
253
		}
254
255
		$data = $this->getRawShare($shareId);
256
		return $this->createShareObject($data);
257
	}
258
259
	/**
260
	 * create federated share and inform the recipient
261
	 *
262
	 * @param IShare $share
263
	 * @return int
264
	 * @throws ShareNotFound
265
	 * @throws \Exception
266
	 */
267
	protected function createFederatedShare(IShare $share) {
268
		$token = $this->tokenHandler->generateToken();
269
		$shareId = $this->addShareToDB(
270
			$share->getNodeId(),
271
			$share->getNodeType(),
272
			$share->getSharedWith(),
273
			$share->getSharedBy(),
274
			$share->getShareOwner(),
275
			$share->getPermissions(),
276
			$token,
277
			$share->getShareType(),
278
			$share->getExpirationDate()
279
		);
280
281
		$failure = false;
282
283
		try {
284
			$sharedByFederatedId = $share->getSharedBy();
285
			if ($this->userManager->userExists($sharedByFederatedId)) {
286
				$cloudId = $this->cloudIdManager->getCloudId($sharedByFederatedId, $this->addressHandler->generateRemoteURL());
287
				$sharedByFederatedId = $cloudId->getId();
288
			}
289
			$ownerCloudId = $this->cloudIdManager->getCloudId($share->getShareOwner(), $this->addressHandler->generateRemoteURL());
290
			$send = $this->notifications->sendRemoteShare(
291
				$token,
292
				$share->getSharedWith(),
293
				$share->getNode()->getName(),
294
				$shareId,
295
				$share->getShareOwner(),
296
				$ownerCloudId->getId(),
297
				$share->getSharedBy(),
298
				$sharedByFederatedId,
299
				$share->getShareType()
300
			);
301
302
			if ($send === false) {
303
				$failure = true;
304
			}
305
		} catch (\Exception $e) {
306
			$this->logger->logException($e, [
307
				'message' => 'Failed to notify remote server of federated share, removing share.',
308
				'level' => ILogger::ERROR,
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

308
				'level' => /** @scrutinizer ignore-deprecated */ ILogger::ERROR,

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...
309
				'app' => 'federatedfilesharing',
310
			]);
311
			$failure = true;
312
		}
313
314
		if ($failure) {
315
			$this->removeShareFromTableById($shareId);
316
			$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.',
317
				[$share->getNode()->getName(), $share->getSharedWith()]);
318
			throw new \Exception($message_t);
319
		}
320
321
		return $shareId;
322
	}
323
324
	/**
325
	 * @param string $shareWith
326
	 * @param IShare $share
327
	 * @param string $shareId internal share Id
328
	 * @return array
329
	 * @throws \Exception
330
	 */
331
	protected function askOwnerToReShare($shareWith, IShare $share, $shareId) {
332
		$remoteShare = $this->getShareFromExternalShareTable($share);
333
		$token = $remoteShare['share_token'];
334
		$remoteId = $remoteShare['remote_id'];
335
		$remote = $remoteShare['remote'];
336
337
		[$token, $remoteId] = $this->notifications->requestReShare(
338
			$token,
339
			$remoteId,
340
			$shareId,
341
			$remote,
342
			$shareWith,
343
			$share->getPermissions(),
344
			$share->getNode()->getName()
345
		);
346
347
		return [$token, $remoteId];
348
	}
349
350
	/**
351
	 * get federated share from the share_external table but exclude mounted link shares
352
	 *
353
	 * @param IShare $share
354
	 * @return array
355
	 * @throws ShareNotFound
356
	 */
357
	protected function getShareFromExternalShareTable(IShare $share) {
358
		$query = $this->dbConnection->getQueryBuilder();
359
		$query->select('*')->from($this->externalShareTable)
360
			->where($query->expr()->eq('user', $query->createNamedParameter($share->getShareOwner())))
361
			->andWhere($query->expr()->eq('mountpoint', $query->createNamedParameter($share->getTarget())));
362
		$qResult = $query->execute();
363
		$result = $qResult->fetchAll();
364
		$qResult->closeCursor();
365
366
		if (isset($result[0]) && (int)$result[0]['remote_id'] > 0) {
367
			return $result[0];
368
		}
369
370
		throw new ShareNotFound('share not found in share_external table');
371
	}
372
373
	/**
374
	 * add share to the database and return the ID
375
	 *
376
	 * @param int $itemSource
377
	 * @param string $itemType
378
	 * @param string $shareWith
379
	 * @param string $sharedBy
380
	 * @param string $uidOwner
381
	 * @param int $permissions
382
	 * @param string $token
383
	 * @param int $shareType
384
	 * @param \DateTime $expirationDate
385
	 * @return int
386
	 */
387
	private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $shareType, $expirationDate) {
388
		$qb = $this->dbConnection->getQueryBuilder();
389
		$qb->insert('share')
390
			->setValue('share_type', $qb->createNamedParameter($shareType))
391
			->setValue('item_type', $qb->createNamedParameter($itemType))
392
			->setValue('item_source', $qb->createNamedParameter($itemSource))
393
			->setValue('file_source', $qb->createNamedParameter($itemSource))
394
			->setValue('share_with', $qb->createNamedParameter($shareWith))
395
			->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
396
			->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
397
			->setValue('permissions', $qb->createNamedParameter($permissions))
398
			->setValue('expiration', $qb->createNamedParameter($expirationDate, IQueryBuilder::PARAM_DATE))
399
			->setValue('token', $qb->createNamedParameter($token))
400
			->setValue('stime', $qb->createNamedParameter(time()));
401
402
		/*
403
		 * Added to fix https://github.com/owncloud/core/issues/22215
404
		 * Can be removed once we get rid of ajax/share.php
405
		 */
406
		$qb->setValue('file_target', $qb->createNamedParameter(''));
407
408
		$qb->execute();
409
		return $qb->getLastInsertId();
410
	}
411
412
	/**
413
	 * Update a share
414
	 *
415
	 * @param IShare $share
416
	 * @return IShare The share object
417
	 */
418
	public function update(IShare $share) {
419
		/*
420
		 * We allow updating the permissions of federated shares
421
		 */
422
		$qb = $this->dbConnection->getQueryBuilder();
423
		$qb->update('share')
424
				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
425
				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
426
				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
427
				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
428
				->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
429
				->execute();
430
431
		// send the updated permission to the owner/initiator, if they are not the same
432
		if ($share->getShareOwner() !== $share->getSharedBy()) {
433
			$this->sendPermissionUpdate($share);
434
		}
435
436
		return $share;
437
	}
438
439
	/**
440
	 * send the updated permission to the owner/initiator, if they are not the same
441
	 *
442
	 * @param IShare $share
443
	 * @throws ShareNotFound
444
	 * @throws \OCP\HintException
445
	 */
446
	protected function sendPermissionUpdate(IShare $share) {
447
		$remoteId = $this->getRemoteId($share);
448
		// if the local user is the owner we send the permission change to the initiator
449
		if ($this->userManager->userExists($share->getShareOwner())) {
450
			[, $remote] = $this->addressHandler->splitUserRemote($share->getSharedBy());
451
		} else { // ... if not we send the permission change to the owner
452
			[, $remote] = $this->addressHandler->splitUserRemote($share->getShareOwner());
453
		}
454
		$this->notifications->sendPermissionChange($remote, $remoteId, $share->getToken(), $share->getPermissions());
455
	}
456
457
458
	/**
459
	 * update successful reShare with the correct token
460
	 *
461
	 * @param int $shareId
462
	 * @param string $token
463
	 */
464
	protected function updateSuccessfulReShare($shareId, $token) {
465
		$query = $this->dbConnection->getQueryBuilder();
466
		$query->update('share')
467
			->where($query->expr()->eq('id', $query->createNamedParameter($shareId)))
468
			->set('token', $query->createNamedParameter($token))
469
			->execute();
470
	}
471
472
	/**
473
	 * store remote ID in federated reShare table
474
	 *
475
	 * @param $shareId
476
	 * @param $remoteId
477
	 */
478
	public function storeRemoteId(int $shareId, string $remoteId): void {
479
		$query = $this->dbConnection->getQueryBuilder();
480
		$query->insert('federated_reshares')
481
			->values(
482
				[
483
					'share_id' => $query->createNamedParameter($shareId),
484
					'remote_id' => $query->createNamedParameter($remoteId),
485
				]
486
			);
487
		$query->execute();
488
	}
489
490
	/**
491
	 * get share ID on remote server for federated re-shares
492
	 *
493
	 * @param IShare $share
494
	 * @return string
495
	 * @throws ShareNotFound
496
	 */
497
	public function getRemoteId(IShare $share): string {
498
		$query = $this->dbConnection->getQueryBuilder();
499
		$query->select('remote_id')->from('federated_reshares')
500
			->where($query->expr()->eq('share_id', $query->createNamedParameter((int)$share->getId())));
501
		$result = $query->execute();
502
		$data = $result->fetch();
503
		$result->closeCursor();
504
505
		if (!is_array($data) || !isset($data['remote_id'])) {
506
			throw new ShareNotFound();
507
		}
508
509
		return (string)$data['remote_id'];
510
	}
511
512
	/**
513
	 * @inheritdoc
514
	 */
515
	public function move(IShare $share, $recipient) {
516
		/*
517
		 * This function does nothing yet as it is just for outgoing
518
		 * federated shares.
519
		 */
520
		return $share;
521
	}
522
523
	/**
524
	 * Get all children of this share
525
	 *
526
	 * @param IShare $parent
527
	 * @return IShare[]
528
	 */
529
	public function getChildren(IShare $parent) {
530
		$children = [];
531
532
		$qb = $this->dbConnection->getQueryBuilder();
533
		$qb->select('*')
534
			->from('share')
535
			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
536
			->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)))
537
			->orderBy('id');
538
539
		$cursor = $qb->execute();
540
		while ($data = $cursor->fetch()) {
541
			$children[] = $this->createShareObject($data);
542
		}
543
		$cursor->closeCursor();
544
545
		return $children;
546
	}
547
548
	/**
549
	 * Delete a share (owner unShares the file)
550
	 *
551
	 * @param IShare $share
552
	 * @throws ShareNotFound
553
	 * @throws \OCP\HintException
554
	 */
555
	public function delete(IShare $share) {
556
		[, $remote] = $this->addressHandler->splitUserRemote($share->getSharedWith());
557
558
		// if the local user is the owner we can send the unShare request directly...
559
		if ($this->userManager->userExists($share->getShareOwner())) {
560
			$this->notifications->sendRemoteUnShare($remote, $share->getId(), $share->getToken());
561
			$this->revokeShare($share, true);
562
		} else { // ... if not we need to correct ID for the unShare request
563
			$remoteId = $this->getRemoteId($share);
564
			$this->notifications->sendRemoteUnShare($remote, $remoteId, $share->getToken());
565
			$this->revokeShare($share, false);
566
		}
567
568
		// only remove the share when all messages are send to not lose information
569
		// about the share to early
570
		$this->removeShareFromTable($share);
571
	}
572
573
	/**
574
	 * in case of a re-share we need to send the other use (initiator or owner)
575
	 * a message that the file was unshared
576
	 *
577
	 * @param IShare $share
578
	 * @param bool $isOwner the user can either be the owner or the user who re-sahred it
579
	 * @throws ShareNotFound
580
	 * @throws \OCP\HintException
581
	 */
582
	protected function revokeShare($share, $isOwner) {
583
		if ($this->userManager->userExists($share->getShareOwner()) && $this->userManager->userExists($share->getSharedBy())) {
584
			// If both the owner and the initiator of the share are local users we don't have to notify anybody else
585
			return;
586
		}
587
588
		// also send a unShare request to the initiator, if this is a different user than the owner
589
		if ($share->getShareOwner() !== $share->getSharedBy()) {
590
			if ($isOwner) {
591
				[, $remote] = $this->addressHandler->splitUserRemote($share->getSharedBy());
592
			} else {
593
				[, $remote] = $this->addressHandler->splitUserRemote($share->getShareOwner());
594
			}
595
			$remoteId = $this->getRemoteId($share);
596
			$this->notifications->sendRevokeShare($remote, $remoteId, $share->getToken());
597
		}
598
	}
599
600
	/**
601
	 * remove share from table
602
	 *
603
	 * @param IShare $share
604
	 */
605
	public function removeShareFromTable(IShare $share) {
606
		$this->removeShareFromTableById($share->getId());
607
	}
608
609
	/**
610
	 * remove share from table
611
	 *
612
	 * @param string $shareId
613
	 */
614
	private function removeShareFromTableById($shareId) {
615
		$qb = $this->dbConnection->getQueryBuilder();
616
		$qb->delete('share')
617
			->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)))
618
			->andWhere($qb->expr()->neq('share_type', $qb->createNamedParameter(IShare::TYPE_CIRCLE)));
619
		$qb->execute();
620
621
		$qb = $this->dbConnection->getQueryBuilder();
622
		$qb->delete('federated_reshares')
623
			->where($qb->expr()->eq('share_id', $qb->createNamedParameter($shareId)));
624
		$qb->execute();
625
	}
626
627
	/**
628
	 * @inheritdoc
629
	 */
630
	public function deleteFromSelf(IShare $share, $recipient) {
631
		// nothing to do here. Technically deleteFromSelf in the context of federated
632
		// shares is a umount of an external storage. This is handled here
633
		// apps/files_sharing/lib/external/manager.php
634
		// TODO move this code over to this app
635
	}
636
637
	public function restore(IShare $share, string $recipient): IShare {
638
		throw new GenericShareException('not implemented');
639
	}
640
641
642
	public function getSharesInFolder($userId, Folder $node, $reshares, $shallow = true) {
643
		$qb = $this->dbConnection->getQueryBuilder();
644
		$qb->select('*')
645
			->from('share', 's')
646
			->andWhere($qb->expr()->orX(
647
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
648
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
649
			))
650
			->andWhere(
651
				$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_REMOTE))
652
			);
653
654
		/**
655
		 * Reshares for this user are shares where they are the owner.
656
		 */
657
		if ($reshares === false) {
658
			$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
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
		$qb->innerJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
669
670
		if ($shallow) {
671
			$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
672
		} else {
673
			$qb->andWhere($qb->expr()->like('f.path', $qb->createNamedParameter($this->dbConnection->escapeLikeParameter($node->getInternalPath()) . '/%')));
674
		}
675
676
		$qb->orderBy('id');
677
678
		$cursor = $qb->execute();
679
		$shares = [];
680
		while ($data = $cursor->fetch()) {
681
			$shares[$data['fileid']][] = $this->createShareObject($data);
682
		}
683
		$cursor->closeCursor();
684
685
		return $shares;
686
	}
687
688
	/**
689
	 * @inheritdoc
690
	 */
691
	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
692
		$qb = $this->dbConnection->getQueryBuilder();
693
		$qb->select('*')
694
			->from('share');
695
696
		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter($shareType)));
697
698
		/**
699
		 * Reshares for this user are shares where they are the owner.
700
		 */
701
		if ($reshares === false) {
702
			//Special case for old shares created via the web UI
703
			$or1 = $qb->expr()->andX(
704
				$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
705
				$qb->expr()->isNull('uid_initiator')
706
			);
707
708
			$qb->andWhere(
709
				$qb->expr()->orX(
710
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
711
					$or1
712
				)
713
			);
714
		} else {
715
			$qb->andWhere(
716
				$qb->expr()->orX(
717
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
718
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
719
				)
720
			);
721
		}
722
723
		if ($node !== null) {
724
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
725
		}
726
727
		if ($limit !== -1) {
728
			$qb->setMaxResults($limit);
729
		}
730
731
		$qb->setFirstResult($offset);
732
		$qb->orderBy('id');
733
734
		$cursor = $qb->execute();
735
		$shares = [];
736
		while ($data = $cursor->fetch()) {
737
			$shares[] = $this->createShareObject($data);
738
		}
739
		$cursor->closeCursor();
740
741
		return $shares;
742
	}
743
744
	/**
745
	 * @inheritdoc
746
	 */
747
	public function getShareById($id, $recipientId = null) {
748
		$qb = $this->dbConnection->getQueryBuilder();
749
750
		$qb->select('*')
751
			->from('share')
752
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
753
			->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)));
754
755
		$cursor = $qb->execute();
756
		$data = $cursor->fetch();
757
		$cursor->closeCursor();
758
759
		if ($data === false) {
760
			throw new ShareNotFound('Can not find share with ID: ' . $id);
761
		}
762
763
		try {
764
			$share = $this->createShareObject($data);
765
		} catch (InvalidShare $e) {
766
			throw new ShareNotFound();
767
		}
768
769
		return $share;
770
	}
771
772
	/**
773
	 * Get shares for a given path
774
	 *
775
	 * @param \OCP\Files\Node $path
776
	 * @return IShare[]
777
	 */
778
	public function getSharesByPath(Node $path) {
779
		$qb = $this->dbConnection->getQueryBuilder();
780
781
		// get federated user shares
782
		$cursor = $qb->select('*')
783
			->from('share')
784
			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
785
			->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)))
786
			->execute();
787
788
		$shares = [];
789
		while ($data = $cursor->fetch()) {
790
			$shares[] = $this->createShareObject($data);
791
		}
792
		$cursor->closeCursor();
793
794
		return $shares;
795
	}
796
797
	/**
798
	 * @inheritdoc
799
	 */
800
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
801
		/** @var IShare[] $shares */
802
		$shares = [];
803
804
		//Get shares directly with this user
805
		$qb = $this->dbConnection->getQueryBuilder();
806
		$qb->select('*')
807
			->from('share');
808
809
		// Order by id
810
		$qb->orderBy('id');
811
812
		// Set limit and offset
813
		if ($limit !== -1) {
814
			$qb->setMaxResults($limit);
815
		}
816
		$qb->setFirstResult($offset);
817
818
		$qb->where($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)));
819
		$qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
820
821
		// Filter by node if provided
822
		if ($node !== null) {
823
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
824
		}
825
826
		$cursor = $qb->execute();
827
828
		while ($data = $cursor->fetch()) {
829
			$shares[] = $this->createShareObject($data);
830
		}
831
		$cursor->closeCursor();
832
833
834
		return $shares;
835
	}
836
837
	/**
838
	 * Get a share by token
839
	 *
840
	 * @param string $token
841
	 * @return IShare
842
	 * @throws ShareNotFound
843
	 */
844
	public function getShareByToken($token) {
845
		$qb = $this->dbConnection->getQueryBuilder();
846
847
		$cursor = $qb->select('*')
848
			->from('share')
849
			->where($qb->expr()->in('share_type', $qb->createNamedParameter($this->supportedShareType, IQueryBuilder::PARAM_INT_ARRAY)))
850
			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
851
			->execute();
852
853
		$data = $cursor->fetch();
854
855
		if ($data === false) {
856
			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
857
		}
858
859
		try {
860
			$share = $this->createShareObject($data);
861
		} catch (InvalidShare $e) {
862
			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
863
		}
864
865
		return $share;
866
	}
867
868
	/**
869
	 * get database row of a give share
870
	 *
871
	 * @param $id
872
	 * @return array
873
	 * @throws ShareNotFound
874
	 */
875
	private function getRawShare($id) {
876
877
		// Now fetch the inserted share and create a complete share object
878
		$qb = $this->dbConnection->getQueryBuilder();
879
		$qb->select('*')
880
			->from('share')
881
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
882
883
		$cursor = $qb->execute();
884
		$data = $cursor->fetch();
885
		$cursor->closeCursor();
886
887
		if ($data === false) {
888
			throw new ShareNotFound;
889
		}
890
891
		return $data;
892
	}
893
894
	/**
895
	 * Create a share object from an database row
896
	 *
897
	 * @param array $data
898
	 * @return IShare
899
	 * @throws InvalidShare
900
	 * @throws ShareNotFound
901
	 */
902
	private function createShareObject($data) {
903
		$share = new Share($this->rootFolder, $this->userManager);
904
		$share->setId((int)$data['id'])
905
			->setShareType((int)$data['share_type'])
906
			->setPermissions((int)$data['permissions'])
907
			->setTarget($data['file_target'])
908
			->setMailSend((bool)$data['mail_send'])
909
			->setToken($data['token']);
910
911
		$shareTime = new \DateTime();
912
		$shareTime->setTimestamp((int)$data['stime']);
913
		$share->setShareTime($shareTime);
914
		$share->setSharedWith($data['share_with']);
915
916
		if ($data['uid_initiator'] !== null) {
917
			$share->setShareOwner($data['uid_owner']);
918
			$share->setSharedBy($data['uid_initiator']);
919
		} else {
920
			//OLD SHARE
921
			$share->setSharedBy($data['uid_owner']);
922
			$path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
923
924
			$owner = $path->getOwner();
925
			$share->setShareOwner($owner->getUID());
926
		}
927
928
		$share->setNodeId((int)$data['file_source']);
929
		$share->setNodeType($data['item_type']);
930
931
		$share->setProviderId($this->identifier());
932
933
		if ($data['expiration'] !== null) {
934
			$expiration = \DateTime::createFromFormat('Y-m-d H:i:s', $data['expiration']);
935
			$share->setExpirationDate($expiration);
936
		}
937
938
		return $share;
939
	}
940
941
	/**
942
	 * Get the node with file $id for $user
943
	 *
944
	 * @param string $userId
945
	 * @param int $id
946
	 * @return \OCP\Files\File|\OCP\Files\Folder
947
	 * @throws InvalidShare
948
	 */
949
	private function getNode($userId, $id) {
950
		try {
951
			$userFolder = $this->rootFolder->getUserFolder($userId);
952
		} catch (NotFoundException $e) {
953
			throw new InvalidShare();
954
		}
955
956
		$nodes = $userFolder->getById($id);
957
958
		if (empty($nodes)) {
959
			throw new InvalidShare();
960
		}
961
962
		return $nodes[0];
963
	}
964
965
	/**
966
	 * A user is deleted from the system
967
	 * So clean up the relevant shares.
968
	 *
969
	 * @param string $uid
970
	 * @param int $shareType
971
	 */
972
	public function userDeleted($uid, $shareType) {
973
		//TODO: probably a good idea to send unshare info to remote servers
974
975
		$qb = $this->dbConnection->getQueryBuilder();
976
977
		$qb->delete('share')
978
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_REMOTE)))
979
			->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
980
			->execute();
981
	}
982
983
	/**
984
	 * This provider does not handle groups
985
	 *
986
	 * @param string $gid
987
	 */
988
	public function groupDeleted($gid) {
989
		// We don't handle groups here
990
	}
991
992
	/**
993
	 * This provider does not handle groups
994
	 *
995
	 * @param string $uid
996
	 * @param string $gid
997
	 */
998
	public function userDeletedFromGroup($uid, $gid) {
999
		// We don't handle groups here
1000
	}
1001
1002
	/**
1003
	 * check if users from other Nextcloud instances are allowed to mount public links share by this instance
1004
	 *
1005
	 * @return bool
1006
	 */
1007
	public function isOutgoingServer2serverShareEnabled() {
1008
		if ($this->gsConfig->onlyInternalFederation()) {
1009
			return false;
1010
		}
1011
		$result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes');
1012
		return ($result === 'yes');
1013
	}
1014
1015
	/**
1016
	 * check if users are allowed to mount public links from other Nextclouds
1017
	 *
1018
	 * @return bool
1019
	 */
1020
	public function isIncomingServer2serverShareEnabled() {
1021
		if ($this->gsConfig->onlyInternalFederation()) {
1022
			return false;
1023
		}
1024
		$result = $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes');
1025
		return ($result === 'yes');
1026
	}
1027
1028
1029
	/**
1030
	 * check if users from other Nextcloud instances are allowed to send federated group shares
1031
	 *
1032
	 * @return bool
1033
	 */
1034
	public function isOutgoingServer2serverGroupShareEnabled() {
1035
		if ($this->gsConfig->onlyInternalFederation()) {
1036
			return false;
1037
		}
1038
		$result = $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no');
1039
		return ($result === 'yes');
1040
	}
1041
1042
	/**
1043
	 * check if users are allowed to receive federated group shares
1044
	 *
1045
	 * @return bool
1046
	 */
1047
	public function isIncomingServer2serverGroupShareEnabled() {
1048
		if ($this->gsConfig->onlyInternalFederation()) {
1049
			return false;
1050
		}
1051
		$result = $this->config->getAppValue('files_sharing', 'incoming_server2server_group_share_enabled', 'no');
1052
		return ($result === 'yes');
1053
	}
1054
1055
	/**
1056
	 * check if federated group sharing is supported, therefore the OCM API need to be enabled
1057
	 *
1058
	 * @return bool
1059
	 */
1060
	public function isFederatedGroupSharingSupported() {
1061
		return $this->cloudFederationProviderManager->isReady();
1062
	}
1063
1064
	/**
1065
	 * Check if querying sharees on the lookup server is enabled
1066
	 *
1067
	 * @return bool
1068
	 */
1069
	public function isLookupServerQueriesEnabled() {
1070
		// in a global scale setup we should always query the lookup server
1071
		if ($this->gsConfig->isGlobalScaleEnabled()) {
1072
			return true;
1073
		}
1074
		$result = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'yes');
1075
		return ($result === 'yes');
1076
	}
1077
1078
1079
	/**
1080
	 * Check if it is allowed to publish user specific data to the lookup server
1081
	 *
1082
	 * @return bool
1083
	 */
1084
	public function isLookupServerUploadEnabled() {
1085
		// in a global scale setup the admin is responsible to keep the lookup server up-to-date
1086
		if ($this->gsConfig->isGlobalScaleEnabled()) {
1087
			return false;
1088
		}
1089
		$result = $this->config->getAppValue('files_sharing', 'lookupServerUploadEnabled', 'yes');
1090
		return ($result === 'yes');
1091
	}
1092
1093
	/**
1094
	 * @inheritdoc
1095
	 */
1096
	public function getAccessList($nodes, $currentAccess) {
1097
		$ids = [];
1098
		foreach ($nodes as $node) {
1099
			$ids[] = $node->getId();
1100
		}
1101
1102
		$qb = $this->dbConnection->getQueryBuilder();
1103
		$qb->select('share_with', 'token', 'file_source')
1104
			->from('share')
1105
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_REMOTE)))
1106
			->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1107
			->andWhere($qb->expr()->orX(
1108
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1109
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1110
			));
1111
		$cursor = $qb->execute();
1112
1113
		if ($currentAccess === false) {
1114
			$remote = $cursor->fetch() !== false;
1115
			$cursor->closeCursor();
1116
1117
			return ['remote' => $remote];
1118
		}
1119
1120
		$remote = [];
1121
		while ($row = $cursor->fetch()) {
1122
			$remote[$row['share_with']] = [
1123
				'node_id' => $row['file_source'],
1124
				'token' => $row['token'],
1125
			];
1126
		}
1127
		$cursor->closeCursor();
1128
1129
		return ['remote' => $remote];
1130
	}
1131
1132
	public function getAllShares(): iterable {
1133
		$qb = $this->dbConnection->getQueryBuilder();
1134
1135
		$qb->select('*')
1136
			->from('share')
1137
			->where(
1138
				$qb->expr()->orX(
1139
					$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_REMOTE)),
1140
					$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_REMOTE_GROUP))
1141
				)
1142
			);
1143
1144
		$cursor = $qb->execute();
1145
		while ($data = $cursor->fetch()) {
1146
			try {
1147
				$share = $this->createShareObject($data);
1148
			} catch (InvalidShare $e) {
1149
				continue;
1150
			} catch (ShareNotFound $e) {
1151
				continue;
1152
			}
1153
1154
			yield $share;
1155
		}
1156
		$cursor->closeCursor();
1157
	}
1158
}
1159