Completed
Push — master ( 91df00...58343d )
by Phil
10:40
created

RequestHandlerController::declineShare()   B

Complexity

Conditions 6
Paths 23

Size

Total Lines 23

Duplication

Lines 23
Ratio 100 %

Importance

Changes 0
Metric Value
cc 6
nc 23
nop 1
dl 23
loc 23
rs 8.9297
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Björn Schießle <[email protected]>
5
 * @author Joas Schilling <[email protected]>
6
 * @author Lukas Reschke <[email protected]>
7
 * @author Morris Jobke <[email protected]>
8
 * @author Thomas Müller <[email protected]>
9
 *
10
 * @copyright Copyright (c) 2018, ownCloud GmbH
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\Controller;
28
29
use OC\OCS\Result;
30
use OCA\FederatedFileSharing\AddressHandler;
31
use OCA\FederatedFileSharing\DiscoveryManager;
32
use OCA\FederatedFileSharing\FederatedShareProvider;
33
use OCA\FederatedFileSharing\FedShareManager;
34
use OCA\FederatedFileSharing\Notifications;
35
use OCA\Files_Sharing\Activity;
36
use OCP\AppFramework\Http;
37
use OCP\AppFramework\OCSController;
38
use OCP\Constants;
39
use OCP\IDBConnection;
40
use OCP\IRequest;
41
use OCP\Share;
42
use OCP\Share\IShare;
43
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
44
use Symfony\Component\EventDispatcher\GenericEvent;
45
46
/**
47
 * Class RequestHandlerController
48
 *
49
 * Handles OCS Request to the federated share API
50
 *
51
 * @package OCA\FederatedFileSharing\API
52
 */
53
class RequestHandlerController extends OCSController {
54
55
	/** @var FederatedShareProvider */
56
	private $federatedShareProvider;
57
58
	/** @var IDBConnection */
59
	private $connection;
60
61
	/** @var Notifications */
62
	private $notifications;
63
64
	/** @var AddressHandler */
65
	private $addressHandler;
66
67
	/** @var  FedShareManager */
68
	private $fedShareManager;
69
70
	/** @var EventDispatcherInterface  */
71
	private $eventDispatcher;
72
73
	/** @var string */
74
	private $shareTable = 'share';
75
76
	/**
77
	 * Server2Server constructor.
78
	 *
79
	 * @param string $appName
80
	 * @param IRequest $request
81
	 * @param FederatedShareProvider $federatedShareProvider
82
	 * @param IDBConnection $connection
83
	 * @param Notifications $notifications
84
	 * @param AddressHandler $addressHandler
85
	 * @param FedShareManager $fedShareManager
86
	 * @param EventDispatcherInterface $eventDispatcher
87
	 */
88
	public function __construct($appName,
89
								IRequest $request,
90
								FederatedShareProvider $federatedShareProvider,
91
								IDBConnection $connection,
92
								Notifications $notifications,
93
								AddressHandler $addressHandler,
94
								FedShareManager $fedShareManager,
95
								EventDispatcherInterface $eventDispatcher
96
	) {
97
		parent::__construct($appName, $request);
98
99
		$this->federatedShareProvider = $federatedShareProvider;
100
		$this->connection = $connection;
101
		$this->notifications = $notifications;
102
		$this->addressHandler = $addressHandler;
103
		$this->fedShareManager = $fedShareManager;
104
		$this->eventDispatcher = $eventDispatcher;
105
	}
106
107
	/**
108
	 * @NoCSRFRequired
109
	 * @PublicPage
110
	 *
111
	 * create a new share
112
	 *
113
	 * @return Result
114
	 */
115
	public function createShare() {
116
		if (!$this->isS2SEnabled(true)) {
117
			return new Result(null, 503, 'Server does not support federated cloud sharing');
118
		}
119
120
		$remote = isset($_POST['remote']) ? $_POST['remote'] : null;
121
		$token = isset($_POST['token']) ? $_POST['token'] : null;
122
		$name = isset($_POST['name']) ? $_POST['name'] : null;
123
		$owner = isset($_POST['owner']) ? $_POST['owner'] : null;
124
		$sharedBy = isset($_POST['sharedBy']) ? $_POST['sharedBy'] : null;
125
		$shareWith = isset($_POST['shareWith']) ? $_POST['shareWith'] : null;
126
		$remoteId = isset($_POST['remoteId']) ? (int)$_POST['remoteId'] : null;
127
		$sharedByFederatedId = isset($_POST['sharedByFederatedId']) ? $_POST['sharedByFederatedId'] : null;
128
		$ownerFederatedId = isset($_POST['ownerFederatedId']) ? $_POST['ownerFederatedId'] : null;
129
130 View Code Duplication
		if ($this->hasNull([$remote, $token, $name, $owner, $remoteId, $shareWith])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
131
			return new Result(null, 400, 'server can not add remote share, missing parameter');
132
		}
133
134
		if (!\OCP\Util::isValidFileName($name)) {
135
			return new Result(null, 400, 'The mountpoint name contains invalid characters.');
136
		}
137
138
		// FIXME this should be a method in the user management instead
139
		\OCP\Util::writeLog('files_sharing', 'shareWith before, ' . $shareWith, \OCP\Util::DEBUG);
140
		\OCP\Util::emitHook(
141
			'\OCA\Files_Sharing\API\Server2Server',
142
			'preLoginNameUsedAsUserName',
143
			['uid' => &$shareWith]
144
		);
145
		\OCP\Util::writeLog('files_sharing', 'shareWith after, ' . $shareWith, \OCP\Util::DEBUG);
146
147
		if (!\OCP\User::userExists($shareWith)) {
148
			return new Result(null, 400, 'User does not exist');
149
		}
150
151
		\OC_Util::setupFS($shareWith);
152
153
		$discoveryManager = new DiscoveryManager(
154
			\OC::$server->getMemCacheFactory(),
155
			\OC::$server->getHTTPClientService()
156
		);
157
		$externalManager = new \OCA\Files_Sharing\External\Manager(
158
			\OC::$server->getDatabaseConnection(),
159
			\OC\Files\Filesystem::getMountManager(),
160
			\OC\Files\Filesystem::getLoader(),
161
			\OC::$server->getNotificationManager(),
162
			\OC::$server->getEventDispatcher(),
163
			$shareWith
164
		);
165
166
		try {
167
			$externalManager->addShare($remote, $token, '', $name, $owner, false, $shareWith, $remoteId);
168
			$shareId = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share_external');
169
			if ($ownerFederatedId === null) {
170
				$ownerFederatedId = $owner . '@' . $this->cleanupRemote($remote);
171
			}
172
			// if the owner of the share and the initiator are the same user
173
			// we also complete the federated share ID for the initiator
174
			if ($sharedByFederatedId === null && $owner === $sharedBy) {
175
				$sharedByFederatedId = $ownerFederatedId;
176
			}
177
178
			$event = new GenericEvent(
179
				null,
180
				[
181
					'name' => $name,
182
					'targetuser' => $sharedByFederatedId,
183
					'owner' => $owner,
184
					'sharewith' => $shareWith,
185
					'sharedby' => $sharedBy,
186
					'remoteid' => $remoteId
187
				]
188
			);
189
			$this->eventDispatcher->dispatch('\OCA\FederatedFileSharing::remote_shareReceived', $event);
190
			\OC::$server->getActivityManager()->publishActivity(
191
				Activity::FILES_SHARING_APP, Activity::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, \trim($name, '/')], '', [],
192
				'', '', $shareWith, Activity::TYPE_REMOTE_SHARE, Activity::PRIORITY_LOW
193
			);
194
195
			$urlGenerator = \OC::$server->getURLGenerator();
196
197
			$notificationManager = \OC::$server->getNotificationManager();
198
			$notification = $notificationManager->createNotification();
199
			$notification->setApp('files_sharing')
200
				->setUser($shareWith)
201
				->setDateTime(new \DateTime())
202
				->setObject('remote_share', $shareId)
203
				->setSubject('remote_share', [$ownerFederatedId, $sharedByFederatedId, \trim($name, '/')])
204
				->setMessage('remote_share', [$ownerFederatedId, $sharedByFederatedId, \trim($name, '/')]);
205
206
			$declineAction = $notification->createAction();
207
			$declineAction->setLabel('decline')
208
				->setLink($urlGenerator->getAbsoluteURL($urlGenerator->linkTo('', 'ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'DELETE');
209
			$notification->addAction($declineAction);
210
211
			$acceptAction = $notification->createAction();
212
			$acceptAction->setLabel('accept')
213
				->setLink($urlGenerator->getAbsoluteURL($urlGenerator->linkTo('', 'ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'POST');
214
			$notification->addAction($acceptAction);
215
216
			$notificationManager->notify($notification);
217
		} catch (\Exception $e) {
218
			\OCP\Util::writeLog('files_sharing', 'server can not add remote share, ' . $e->getMessage(), \OCP\Util::ERROR);
219
			return new Result(null, 500, 'internal server error, was not able to add share from ' . $remote);
220
		}
221
222
		return new Result();
223
	}
224
225
	/**
226
	 * @NoCSRFRequired
227
	 * @PublicPage
228
	 *
229
	 * create re-share on behalf of another user
230
	 *
231
	 * @param int $id
232
	 *
233
	 * @return Result
234
	 */
235
	public function reShare($id) {
236
		$token = $this->request->getParam('token', null);
237
		$shareWith = $this->request->getParam('shareWith', null);
238
		$permission = (int)$this->request->getParam('permission', null);
239
		$remoteId = (int)$this->request->getParam('remoteId', null);
240
241 View Code Duplication
		if ($this->hasNull([$id, $token, $shareWith, $permission, $remoteId])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
242
			return new Result(null, Http::STATUS_BAD_REQUEST);
243
		}
244
245
		try {
246
			$share = $this->federatedShareProvider->getShareById($id);
247
248
			// don't allow to share a file back to the owner
249
			list($user, $remote) = $this->addressHandler->splitUserRemote($shareWith);
250
			$owner = $share->getShareOwner();
251
			$currentServer = $this->addressHandler->generateRemoteURL();
252
			if ($this->addressHandler->compareAddresses($user, $remote, $owner, $currentServer)
253
				|| !$this->verifyShare($share, $token)
254
			) {
255
				return new Result(null, Http::STATUS_FORBIDDEN);
256
			}
257
258
			// check if re-sharing is allowed
259
			if (!$share->getPermissions() | ~Constants::PERMISSION_SHARE) {
260
				return new Result(null, Http::STATUS_BAD_REQUEST);
261
			}
262
			$share->setPermissions($share->getPermissions() & $permission);
263
			// the recipient of the initial share is now the initiator for the re-share
264
			$share->setSharedBy($share->getSharedWith());
265
			$share->setSharedWith($shareWith);
266
267
			$result = $this->federatedShareProvider->create($share);
268
			$this->federatedShareProvider->storeRemoteId((int)$result->getId(), $remoteId);
269
		} catch (Share\Exceptions\ShareNotFound $e) {
270
			return new Result(null, Http::STATUS_NOT_FOUND);
271
		} catch (\Exception $e) {
272
			return new Result(null, Http::STATUS_BAD_REQUEST);
273
		}
274
275
		return new Result(['token' => $result->getToken(), 'remoteId' => $result->getId()]);
276
	}
277
278
	/**
279
	 * @NoCSRFRequired
280
	 * @PublicPage
281
	 *
282
	 * accept server-to-server share
283
	 *
284
	 * @param int $id
285
	 *
286
	 * @return Result
287
	 */
288 View Code Duplication
	public function acceptShare($id) {
289
		if (!$this->isS2SEnabled()) {
290
			return new Result(null, 503, 'Server does not support federated cloud sharing');
291
		}
292
293
		$token = isset($_POST['token']) ? $_POST['token'] : null;
294
295
		try {
296
			$share = $this->federatedShareProvider->getShareById($id);
297
			if ($this->verifyShare($share, $token)) {
298
				$this->fedShareManager->acceptShare($share);
299
				if ($share->getShareOwner() !== $share->getSharedBy()) {
300
					list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
301
					$remoteId = $this->federatedShareProvider->getRemoteId($share);
302
					$this->notifications->sendAcceptShare($remote, $remoteId, $share->getToken());
303
				}
304
			}
305
		} catch (Share\Exceptions\ShareNotFound $e) {
306
			// pass
307
		}
308
		return new Result();
309
	}
310
311
	/**
312
	 * @NoCSRFRequired
313
	 * @PublicPage
314
	 *
315
	 * decline server-to-server share
316
	 *
317
	 * @param int $id
318
	 *
319
	 * @return Result
320
	 */
321 View Code Duplication
	public function declineShare($id) {
322
		if (!$this->isS2SEnabled()) {
323
			return new Result(null, 503, 'Server does not support federated cloud sharing');
324
		}
325
326
		$token = isset($_POST['token']) ? $_POST['token'] : null;
327
328
		try {
329
			$share = $this->federatedShareProvider->getShareById($id);
330
			if ($this->verifyShare($share, $token)) {
331
				if ($share->getShareOwner() !== $share->getSharedBy()) {
332
					list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
333
					$remoteId = $this->federatedShareProvider->getRemoteId($share);
334
					$this->notifications->sendDeclineShare($remote, $remoteId, $share->getToken());
335
				}
336
				$this->fedShareManager->declineShare($share);
337
			}
338
		} catch (Share\Exceptions\ShareNotFound $e) {
339
			// pass
340
		}
341
342
		return new Result();
343
	}
344
345
	/**
346
	 * @NoCSRFRequired
347
	 * @PublicPage
348
	 *
349
	 * remove server-to-server share if it was unshared by the owner
350
	 *
351
	 * @param int $id
352
	 *
353
	 * @return Result
354
	 */
355
	public function unshare($id) {
356
		if (!$this->isS2SEnabled()) {
357
			return new Result(null, 503, 'Server does not support federated cloud sharing');
358
		}
359
360
		$token = isset($_POST['token']) ? $_POST['token'] : null;
361
362
		$query = \OCP\DB::prepare('SELECT * FROM `*PREFIX*share_external` WHERE `remote_id` = ? AND `share_token` = ?');
363
		$query->execute([$id, $token]);
364
		$share = $query->fetchRow();
365
366
		if ($token && $id && !empty($share)) {
367
			$remote = $this->cleanupRemote($share['remote']);
368
369
			$owner = $share['owner'] . '@' . $remote;
370
			$mountpoint = $share['mountpoint'];
371
			$user = $share['user'];
372
373
			$query = \OCP\DB::prepare('DELETE FROM `*PREFIX*share_external` WHERE `remote_id` = ? AND `share_token` = ?');
374
			$query->execute([$id, $token]);
375
376
			if ($share['accepted']) {
377
				$path = \trim($mountpoint, '/');
378
			} else {
379
				$path = \trim($share['name'], '/');
380
			}
381
382
			$notificationManager = \OC::$server->getNotificationManager();
383
			$notification = $notificationManager->createNotification();
384
			$notification->setApp('files_sharing')
385
				->setUser($share['user'])
386
				->setObject('remote_share', (int) $share['id']);
387
			$notificationManager->markProcessed($notification);
388
389
			\OC::$server->getActivityManager()->publishActivity(
390
				Activity::FILES_SHARING_APP, Activity::SUBJECT_REMOTE_SHARE_UNSHARED, [$owner, $path], '', [],
391
				'', '', $user, Activity::TYPE_REMOTE_SHARE, Activity::PRIORITY_MEDIUM);
392
		}
393
394
		return new Result();
395
	}
396
397
	private function cleanupRemote($remote) {
398
		$remote = \substr($remote, \strpos($remote, '://') + 3);
399
400
		return \rtrim($remote, '/');
401
	}
402
403
	/**
404
	 * @NoCSRFRequired
405
	 * @PublicPage
406
	 *
407
	 * federated share was revoked, either by the owner or the re-sharer
408
	 *
409
	 * @param int $id
410
	 *
411
	 * @return Result
412
	 */
413
	public function revoke($id) {
414
		$token = $this->request->getParam('token');
415
		
416
		$share = $this->federatedShareProvider->getShareById($id);
417
		if (!$this->verifyShare($share, $token)) {
418
			return new Result(null, Http::STATUS_BAD_REQUEST);
419
		}
420
421
		$this->federatedShareProvider->removeShareFromTable($share);
422
		return new Result();
423
	}
424
	
425
	/**
426
	 * get share
427
	 *
428
	 * @param int $id
429
	 * @param string $token
430
	 *
431
	 * @return array|bool
432
	 */
433
	protected function getShare($id, $token) {
434
		$query = $this->connection->getQueryBuilder();
435
		$query->select('*')->from($this->shareTable)
436
			->where($query->expr()->eq('token', $query->createNamedParameter($token)))
437
			->andWhere($query->expr()->eq('share_type', $query->createNamedParameter(FederatedShareProvider::SHARE_TYPE_REMOTE)))
438
			->andWhere($query->expr()->eq('id', $query->createNamedParameter($id)));
439
440
		$result = $query->execute()->fetchAll();
441
442
		if (!empty($result) && isset($result[0])) {
443
			return $result[0];
444
		}
445
446
		return false;
447
	}
448
449
	/**
450
	 * check if server-to-server sharing is enabled
451
	 *
452
	 * @param bool $incoming
453
	 *
454
	 * @return bool
455
	 */
456
	private function isS2SEnabled($incoming = false) {
457
		$result = \OCP\App::isEnabled('files_sharing');
458
459
		if ($incoming) {
460
			$result = $result && $this->federatedShareProvider->isIncomingServer2serverShareEnabled();
461
		} else {
462
			$result = $result && $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
463
		}
464
465
		return $result;
466
	}
467
468
	/**
469
	 * check if we got the right share
470
	 *
471
	 * @param Share\IShare $share
472
	 * @param string $token
473
	 *
474
	 * @return bool
475
	 */
476
	protected function verifyShare(Share\IShare $share, $token) {
477
		if (
478
			$share->getShareType() === FederatedShareProvider::SHARE_TYPE_REMOTE &&
479
			$share->getToken() === $token
480
		) {
481
			return true;
482
		}
483
484
		return false;
485
	}
486
487
	/**
488
	 * @NoCSRFRequired
489
	 * @PublicPage
490
	 *
491
	 * update share information to keep federated re-shares in sync
492
	 *
493
	 * @param array $params
494
	 *
495
	 * @return Result
496
	 */
497
	public function updatePermissions($params) {
498
		$id = (int)$params['id'];
499
		$token = $this->request->getParam('token', null);
500
		$permissions = $this->request->getParam('permissions', null);
501
502
		try {
503
			$share = $this->federatedShareProvider->getShareById($id);
504
			$validPermission = \ctype_digit($permissions);
505
			$validToken = $this->verifyShare($share, $token);
506
			if (!$validPermission || !$validToken) {
507
				return new Result(null, Http::STATUS_BAD_REQUEST);
508
			}
509
			$this->updatePermissionsInDatabase($share, (int)$permissions);
510
		} catch (Share\Exceptions\ShareNotFound $e) {
511
			return new Result(null, Http::STATUS_BAD_REQUEST);
512
		}
513
514
		return new Result();
515
	}
516
517
	/**
518
	 * update permissions in database
519
	 *
520
	 * @param IShare $share
521
	 * @param int $permissions
522
	 */
523 View Code Duplication
	protected function updatePermissionsInDatabase(IShare $share, $permissions) {
524
		$query = $this->connection->getQueryBuilder();
525
		$query->update('share')
526
			->where($query->expr()->eq('id', $query->createNamedParameter($share->getId())))
527
			->set('permissions', $query->createNamedParameter($permissions))
528
			->execute();
529
	}
530
531
	/**
532
	 * Check if value is null or an array has any null item
533
	 *
534
	 * @param mixed $param
535
	 *
536
	 * @return bool
537
	 */
538
	protected function hasNull($param) {
539
		if (\is_array($param)) {
540
			return \in_array(null, $param, true);
541
		} else {
542
			return $param === null;
543
		}
544
	}
545
}
546