Passed
Push — master ( aeb32e...81302f )
by Christoph
15:20 queued 10s
created

Manager::addShare()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 51
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 36
nc 12
nop 10
dl 0
loc 51
rs 9.0328
c 0
b 0
f 0

How to fix   Long Method    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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 Christoph Wurst <[email protected]>
8
 * @author Daniel Hansson <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Jörn Friedrich Dreyer <[email protected]>
11
 * @author Julius Härtl <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Morris Jobke <[email protected]>
14
 * @author Robin Appelman <[email protected]>
15
 * @author Roeland Jago Douma <[email protected]>
16
 * @author Stefan Weil <[email protected]>
17
 *
18
 * @license AGPL-3.0
19
 *
20
 * This code is free software: you can redistribute it and/or modify
21
 * it under the terms of the GNU Affero General Public License, version 3,
22
 * as published by the Free Software Foundation.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
 * GNU Affero General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU Affero General Public License, version 3,
30
 * along with this program. If not, see <http://www.gnu.org/licenses/>
31
 *
32
 */
33
34
namespace OCA\Files_Sharing\External;
35
36
use Doctrine\DBAL\Driver\Exception;
37
use OC\Files\Filesystem;
38
use OCA\FederatedFileSharing\Events\FederatedShareAddedEvent;
39
use OCA\Files_Sharing\Helper;
40
use OCP\EventDispatcher\IEventDispatcher;
41
use OCP\Federation\ICloudFederationFactory;
42
use OCP\Federation\ICloudFederationProviderManager;
43
use OCP\Files;
44
use OCP\Files\Storage\IStorageFactory;
45
use OCP\Http\Client\IClientService;
46
use OCP\IDBConnection;
47
use OCP\IGroupManager;
48
use OCP\IUserManager;
49
use OCP\Notification\IManager;
50
use OCP\OCS\IDiscoveryService;
51
use OCP\Share;
52
use OCP\Share\IShare;
53
54
class Manager {
55
	public const STORAGE = '\OCA\Files_Sharing\External\Storage';
56
57
	/** @var string|null */
58
	private $uid;
59
60
	/** @var IDBConnection */
61
	private $connection;
62
63
	/** @var \OC\Files\Mount\Manager */
64
	private $mountManager;
65
66
	/** @var IStorageFactory */
67
	private $storageLoader;
68
69
	/** @var IClientService */
70
	private $clientService;
71
72
	/** @var IManager */
73
	private $notificationManager;
74
75
	/** @var IDiscoveryService */
76
	private $discoveryService;
77
78
	/** @var ICloudFederationProviderManager */
79
	private $cloudFederationProviderManager;
80
81
	/** @var ICloudFederationFactory */
82
	private $cloudFederationFactory;
83
84
	/** @var IGroupManager  */
85
	private $groupManager;
86
87
	/** @var IUserManager */
88
	private $userManager;
89
90
	/** @var IEventDispatcher */
91
	private $eventDispatcher;
92
93
	public function __construct(IDBConnection $connection,
94
								\OC\Files\Mount\Manager $mountManager,
95
								IStorageFactory $storageLoader,
96
								IClientService $clientService,
97
								IManager $notificationManager,
98
								IDiscoveryService $discoveryService,
99
								ICloudFederationProviderManager $cloudFederationProviderManager,
100
								ICloudFederationFactory $cloudFederationFactory,
101
								IGroupManager $groupManager,
102
								IUserManager $userManager,
103
								?string $uid,
104
								IEventDispatcher $eventDispatcher) {
105
		$this->connection = $connection;
106
		$this->mountManager = $mountManager;
107
		$this->storageLoader = $storageLoader;
108
		$this->clientService = $clientService;
109
		$this->uid = $uid;
110
		$this->notificationManager = $notificationManager;
111
		$this->discoveryService = $discoveryService;
112
		$this->cloudFederationProviderManager = $cloudFederationProviderManager;
113
		$this->cloudFederationFactory = $cloudFederationFactory;
114
		$this->groupManager = $groupManager;
115
		$this->userManager = $userManager;
116
		$this->eventDispatcher = $eventDispatcher;
117
	}
118
119
	/**
120
	 * add new server-to-server share
121
	 *
122
	 * @param string $remote
123
	 * @param string $token
124
	 * @param string $password
125
	 * @param string $name
126
	 * @param string $owner
127
	 * @param int $shareType
128
	 * @param boolean $accepted
129
	 * @param string $user
130
	 * @param string $remoteId
131
	 * @param int $parent
132
	 * @return Mount|null
133
	 * @throws \Doctrine\DBAL\Exception
134
	 */
135
	public function addShare($remote, $token, $password, $name, $owner, $shareType, $accepted = false, $user = null, $remoteId = '', $parent = -1) {
136
		$user = $user ? $user : $this->uid;
137
		$accepted = $accepted ? IShare::STATUS_ACCEPTED : IShare::STATUS_PENDING;
138
		$name = Filesystem::normalizePath('/' . $name);
139
140
		if ($accepted !== IShare::STATUS_ACCEPTED) {
141
			// To avoid conflicts with the mount point generation later,
142
			// we only use a temporary mount point name here. The real
143
			// mount point name will be generated when accepting the share,
144
			// using the original share item name.
145
			$tmpMountPointName = '{{TemporaryMountPointName#' . $name . '}}';
146
			$mountPoint = $tmpMountPointName;
147
			$hash = md5($tmpMountPointName);
148
			$data = [
149
				'remote' => $remote,
150
				'share_token' => $token,
151
				'password' => $password,
152
				'name' => $name,
153
				'owner' => $owner,
154
				'user' => $user,
155
				'mountpoint' => $mountPoint,
156
				'mountpoint_hash' => $hash,
157
				'accepted' => $accepted,
158
				'remote_id' => $remoteId,
159
				'share_type' => $shareType,
160
			];
161
162
			$i = 1;
163
			while (!$this->connection->insertIfNotExist('*PREFIX*share_external', $data, ['user', 'mountpoint_hash'])) {
164
				// The external share already exists for the user
165
				$data['mountpoint'] = $tmpMountPointName . '-' . $i;
166
				$data['mountpoint_hash'] = md5($data['mountpoint']);
167
				$i++;
168
			}
169
			return null;
170
		}
171
172
		$mountPoint = Files::buildNotExistingFileName('/', $name);
173
		$mountPoint = Filesystem::normalizePath('/' . $mountPoint);
174
		$hash = md5($mountPoint);
175
176
		$this->writeShareToDb($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType);
177
178
		$options = [
179
			'remote' => $remote,
180
			'token' => $token,
181
			'password' => $password,
182
			'mountpoint' => $mountPoint,
183
			'owner' => $owner
184
		];
185
		return $this->mountShare($options);
186
	}
187
188
	/**
189
	 * write remote share to the database
190
	 *
191
	 * @param $remote
192
	 * @param $token
193
	 * @param $password
194
	 * @param $name
195
	 * @param $owner
196
	 * @param $user
197
	 * @param $mountPoint
198
	 * @param $hash
199
	 * @param $accepted
200
	 * @param $remoteId
201
	 * @param $parent
202
	 * @param $shareType
203
	 *
204
	 * @return void
205
	 * @throws \Doctrine\DBAL\Driver\Exception
206
	 */
207
	private function writeShareToDb($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType): void {
208
		$query = $this->connection->prepare('
209
				INSERT INTO `*PREFIX*share_external`
210
					(`remote`, `share_token`, `password`, `name`, `owner`, `user`, `mountpoint`, `mountpoint_hash`, `accepted`, `remote_id`, `parent`, `share_type`)
211
				VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
212
			');
213
		$query->execute([$remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType]);
214
	}
215
216
	/**
217
	 * get share
218
	 *
219
	 * @param int $id share id
220
	 * @return mixed share of false
221
	 */
222
	public function getShare($id) {
223
		$getShare = $this->connection->prepare('
224
			SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`, `parent`, `share_type`, `password`, `mountpoint_hash`
225
			FROM  `*PREFIX*share_external`
226
			WHERE `id` = ?');
227
		$result = $getShare->execute([$id]);
228
		$share = $result->fetch();
229
		$result->closeCursor();
230
		$validShare = is_array($share) && isset($share['share_type']) && isset($share['user']);
231
232
		// check if the user is allowed to access it
233
		if ($validShare && (int)$share['share_type'] === IShare::TYPE_USER && $share['user'] === $this->uid) {
234
			return $share;
235
		} elseif ($validShare && (int)$share['share_type'] === IShare::TYPE_GROUP) {
236
			$user = $this->userManager->get($this->uid);
237
			if ($this->groupManager->get($share['user'])->inGroup($user)) {
238
				return $share;
239
			}
240
		}
241
242
		return false;
243
	}
244
245
	/**
246
	 * accept server-to-server share
247
	 *
248
	 * @param int $id
249
	 * @return bool True if the share could be accepted, false otherwise
250
	 */
251
	public function acceptShare($id) {
252
		$share = $this->getShare($id);
253
		$result = false;
254
255
		if ($share) {
256
			\OC_Util::setupFS($this->uid);
257
			$shareFolder = Helper::getShareFolder();
258
			$mountPoint = Files::buildNotExistingFileName($shareFolder, $share['name']);
259
			$mountPoint = Filesystem::normalizePath($mountPoint);
260
			$hash = md5($mountPoint);
261
			$userShareAccepted = false;
262
263
			if ((int)$share['share_type'] === IShare::TYPE_USER) {
264
				$acceptShare = $this->connection->prepare('
265
				UPDATE `*PREFIX*share_external`
266
				SET `accepted` = ?,
267
					`mountpoint` = ?,
268
					`mountpoint_hash` = ?
269
				WHERE `id` = ? AND `user` = ?');
270
				$userShareAccepted = $acceptShare->execute([1, $mountPoint, $hash, $id, $this->uid]);
271
			} else {
272
				try {
273
					$this->writeShareToDb(
274
						$share['remote'],
275
						$share['share_token'],
276
						$share['password'],
277
						$share['name'],
278
						$share['owner'],
279
						$this->uid,
280
						$mountPoint, $hash, 1,
281
						$share['remote_id'],
282
						$id,
283
						$share['share_type']);
284
					$result = true;
285
				} catch (Exception $e) {
286
					$result = false;
287
				}
288
			}
289
			if ($userShareAccepted !== false) {
290
				$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'accept');
291
				$event = new FederatedShareAddedEvent($share['remote']);
292
				$this->eventDispatcher->dispatchTyped($event);
293
				$result = true;
294
			}
295
		}
296
297
		// Make sure the user has no notification for something that does not exist anymore.
298
		$this->processNotification($id);
299
300
		return $result;
301
	}
302
303
	/**
304
	 * decline server-to-server share
305
	 *
306
	 * @param int $id
307
	 * @return bool True if the share could be declined, false otherwise
308
	 */
309
	public function declineShare($id) {
310
		$share = $this->getShare($id);
311
		$result = false;
312
313
		if ($share && (int)$share['share_type'] === IShare::TYPE_USER) {
314
			$removeShare = $this->connection->prepare('
315
				DELETE FROM `*PREFIX*share_external` WHERE `id` = ? AND `user` = ?');
316
			$removeShare->execute([$id, $this->uid]);
317
			$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
318
319
			$this->processNotification($id);
320
			$result = true;
321
		} elseif ($share && (int)$share['share_type'] === IShare::TYPE_GROUP) {
322
			try {
323
				$this->writeShareToDb(
324
					$share['remote'],
325
					$share['share_token'],
326
					$share['password'],
327
					$share['name'],
328
					$share['owner'],
329
					$this->uid,
330
					$share['mountpoint'],
331
					$share['mountpoint_hash'],
332
					0,
333
					$share['remote_id'],
334
					$id,
335
					$share['share_type']);
336
				$result = true;
337
			} catch (Exception $e) {
338
				$result = false;
339
			}
340
			$this->processNotification($id);
341
		}
342
343
		return $result;
344
	}
345
346
	/**
347
	 * @param int $remoteShare
348
	 */
349
	public function processNotification($remoteShare) {
350
		$filter = $this->notificationManager->createNotification();
351
		$filter->setApp('files_sharing')
352
			->setUser($this->uid)
353
			->setObject('remote_share', (int) $remoteShare);
354
		$this->notificationManager->markProcessed($filter);
355
	}
356
357
	/**
358
	 * inform remote server whether server-to-server share was accepted/declined
359
	 *
360
	 * @param string $remote
361
	 * @param string $token
362
	 * @param string $remoteId Share id on the remote host
363
	 * @param string $feedback
364
	 * @return boolean
365
	 */
366
	private function sendFeedbackToRemote($remote, $token, $remoteId, $feedback) {
367
		$result = $this->tryOCMEndPoint($remote, $token, $remoteId, $feedback);
368
369
		if ($result === true) {
370
			return true;
371
		}
372
373
		$federationEndpoints = $this->discoveryService->discover($remote, 'FEDERATED_SHARING');
374
		$endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares';
375
376
		$url = rtrim($remote, '/') . $endpoint . '/' . $remoteId . '/' . $feedback . '?format=' . Share::RESPONSE_FORMAT;
377
		$fields = ['token' => $token];
378
379
		$client = $this->clientService->newClient();
380
381
		try {
382
			$response = $client->post(
383
				$url,
384
				[
385
					'body' => $fields,
386
					'connect_timeout' => 10,
387
				]
388
			);
389
		} catch (\Exception $e) {
390
			return false;
391
		}
392
393
		$status = json_decode($response->getBody(), true);
394
395
		return ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200);
396
	}
397
398
	/**
399
	 * try send accept message to ocm end-point
400
	 *
401
	 * @param string $remoteDomain
402
	 * @param string $token
403
	 * @param string $remoteId id of the share
404
	 * @param string $feedback
405
	 * @return bool
406
	 */
407
	protected function tryOCMEndPoint($remoteDomain, $token, $remoteId, $feedback) {
408
		switch ($feedback) {
409
			case 'accept':
410
				$notification = $this->cloudFederationFactory->getCloudFederationNotification();
411
				$notification->setMessage(
412
					'SHARE_ACCEPTED',
413
					'file',
414
					$remoteId,
415
					[
416
						'sharedSecret' => $token,
417
						'message' => 'Recipient accept the share'
418
					]
419
420
				);
421
				return $this->cloudFederationProviderManager->sendNotification($remoteDomain, $notification);
422
			case 'decline':
423
				$notification = $this->cloudFederationFactory->getCloudFederationNotification();
424
				$notification->setMessage(
425
					'SHARE_DECLINED',
426
					'file',
427
					$remoteId,
428
					[
429
						'sharedSecret' => $token,
430
						'message' => 'Recipient declined the share'
431
					]
432
433
				);
434
				return $this->cloudFederationProviderManager->sendNotification($remoteDomain, $notification);
435
		}
436
437
		return false;
438
	}
439
440
441
	/**
442
	 * remove '/user/files' from the path and trailing slashes
443
	 *
444
	 * @param string $path
445
	 * @return string
446
	 */
447
	protected function stripPath($path) {
448
		$prefix = '/' . $this->uid . '/files';
449
		return rtrim(substr($path, strlen($prefix)), '/');
450
	}
451
452
	public function getMount($data) {
453
		$data['manager'] = $this;
454
		$mountPoint = '/' . $this->uid . '/files' . $data['mountpoint'];
455
		$data['mountpoint'] = $mountPoint;
456
		$data['certificateManager'] = \OC::$server->getCertificateManager();
457
		return new Mount(self::STORAGE, $mountPoint, $data, $this, $this->storageLoader);
458
	}
459
460
	/**
461
	 * @param array $data
462
	 * @return Mount
463
	 */
464
	protected function mountShare($data) {
465
		$mount = $this->getMount($data);
466
		$this->mountManager->addMount($mount);
467
		return $mount;
468
	}
469
470
	/**
471
	 * @return \OC\Files\Mount\Manager
472
	 */
473
	public function getMountManager() {
474
		return $this->mountManager;
475
	}
476
477
	/**
478
	 * @param string $source
479
	 * @param string $target
480
	 * @return bool
481
	 */
482
	public function setMountPoint($source, $target) {
483
		$source = $this->stripPath($source);
484
		$target = $this->stripPath($target);
485
		$sourceHash = md5($source);
486
		$targetHash = md5($target);
487
488
		$query = $this->connection->prepare('
489
			UPDATE `*PREFIX*share_external`
490
			SET `mountpoint` = ?, `mountpoint_hash` = ?
491
			WHERE `mountpoint_hash` = ?
492
			AND `user` = ?
493
		');
494
		$result = (bool)$query->execute([$target, $targetHash, $sourceHash, $this->uid]);
495
496
		return $result;
497
	}
498
499
	public function removeShare($mountPoint): bool {
500
		$mountPointObj = $this->mountManager->find($mountPoint);
501
		$id = $mountPointObj->getStorage()->getCache()->getId('');
502
503
		$mountPoint = $this->stripPath($mountPoint);
504
		$hash = md5($mountPoint);
505
506
		try {
507
			$getShare = $this->connection->prepare('
508
				SELECT `remote`, `share_token`, `remote_id`, `share_type`, `id`
509
				FROM  `*PREFIX*share_external`
510
				WHERE `mountpoint_hash` = ? AND `user` = ?');
511
			$result = $getShare->execute([$hash, $this->uid]);
512
			$share = $result->fetch();
513
			$result->closeCursor();
514
			if ($share !== false && (int)$share['share_type'] === IShare::TYPE_USER) {
515
				try {
516
					$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
517
				} catch (\Throwable $e) {
518
					// if we fail to notify the remote (probably cause the remote is down)
519
					// we still want the share to be gone to prevent undeletable remotes
520
				}
521
522
				$query = $this->connection->prepare('
523
				DELETE FROM `*PREFIX*share_external`
524
				WHERE `id` = ?
525
				');
526
				$deleteResult = $query->execute([(int)$share['id']]);
527
				$deleteResult->closeCursor();
528
			} elseif ($share !== false && (int)$share['share_type'] === IShare::TYPE_GROUP) {
529
				$query = $this->connection->prepare('
530
					UPDATE `*PREFIX*share_external`
531
					SET `accepted` = ?
532
					WHERE `id` = ?');
533
				$updateResult = $query->execute([0, (int)$share['id']]);
534
				$updateResult->closeCursor();
535
			}
536
537
			$this->removeReShares($id);
538
		} catch (\Doctrine\DBAL\Exception $ex) {
539
			return false;
540
		}
541
542
		return true;
543
	}
544
545
	/**
546
	 * remove re-shares from share table and mapping in the federated_reshares table
547
	 *
548
	 * @param $mountPointId
549
	 */
550
	protected function removeReShares($mountPointId) {
551
		$selectQuery = $this->connection->getQueryBuilder();
552
		$query = $this->connection->getQueryBuilder();
553
		$selectQuery->select('id')->from('share')
554
			->where($selectQuery->expr()->eq('file_source', $query->createNamedParameter($mountPointId)));
555
		$select = $selectQuery->getSQL();
556
557
558
		$query->delete('federated_reshares')
559
			->where($query->expr()->in('share_id', $query->createFunction('(' . $select . ')')));
560
		$query->execute();
561
562
		$deleteReShares = $this->connection->getQueryBuilder();
563
		$deleteReShares->delete('share')
564
			->where($deleteReShares->expr()->eq('file_source', $deleteReShares->createNamedParameter($mountPointId)));
565
		$deleteReShares->execute();
566
	}
567
568
	/**
569
	 * remove all shares for user $uid if the user was deleted
570
	 *
571
	 * @param string $uid
572
	 */
573
	public function removeUserShares($uid): bool {
574
		try {
575
			$getShare = $this->connection->prepare('
576
				SELECT `remote`, `share_token`, `remote_id`
577
				FROM  `*PREFIX*share_external`
578
				WHERE `user` = ?');
579
			$result = $getShare->execute([$uid]);
580
			$shares = $result->fetchAll();
581
			$result->closeCursor();
582
			foreach ($shares as $share) {
583
				$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
584
			}
585
586
			$query = $this->connection->prepare('
587
				DELETE FROM `*PREFIX*share_external`
588
				WHERE `user` = ?
589
			');
590
			$deleteResult = $query->execute([$uid]);
591
			$deleteResult->closeCursor();
592
		} catch (\Doctrine\DBAL\Exception $ex) {
593
			return false;
594
		}
595
596
		return true;
597
	}
598
599
	/**
600
	 * return a list of shares which are not yet accepted by the user
601
	 *
602
	 * @return array list of open server-to-server shares
603
	 */
604
	public function getOpenShares() {
605
		return $this->getShares(false);
606
	}
607
608
	/**
609
	 * return a list of shares which are accepted by the user
610
	 *
611
	 * @return array list of accepted server-to-server shares
612
	 */
613
	public function getAcceptedShares() {
614
		return $this->getShares(true);
615
	}
616
617
	/**
618
	 * return a list of shares for the user
619
	 *
620
	 * @param bool|null $accepted True for accepted only,
621
	 *                            false for not accepted,
622
	 *                            null for all shares of the user
623
	 * @return array list of open server-to-server shares
624
	 */
625
	private function getShares($accepted) {
626
		$user = $this->userManager->get($this->uid);
627
		$groups = $this->groupManager->getUserGroups($user);
628
		$userGroups = [];
629
		foreach ($groups as $group) {
630
			$userGroups[] = $group->getGID();
631
		}
632
633
		$query = 'SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`
634
		          FROM `*PREFIX*share_external`
635
				  WHERE (`user` = ? OR `user` IN (?))';
636
		$parameters = [$this->uid, implode(',',$userGroups)];
637
		if (!is_null($accepted)) {
638
			$query .= ' AND `accepted` = ?';
639
			$parameters[] = (int) $accepted;
640
		}
641
		$query .= ' ORDER BY `id` ASC';
642
643
		$sharesQuery = $this->connection->prepare($query);
644
		try {
645
			$result = $sharesQuery->execute($parameters);
646
			$shares = $result->fetchAll();
647
			$result->closeCursor();
648
			return $shares;
649
		} catch (\Doctrine\DBAL\Exception $e) {
650
			return [];
651
		}
652
	}
653
}
654