Completed
Push — master ( 830834...005b3d )
by Thomas
10:38
created

Manager::stripPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Björn Schießle <[email protected]>
4
 * @author Joas Schilling <[email protected]>
5
 * @author Jörn Friedrich Dreyer <[email protected]>
6
 * @author Lukas Reschke <[email protected]>
7
 * @author Morris Jobke <[email protected]>
8
 * @author Robin Appelman <[email protected]>
9
 * @author Roeland Jago Douma <[email protected]>
10
 * @author Stefan Weil <[email protected]>
11
 *
12
 * @copyright Copyright (c) 2016, ownCloud GmbH.
13
 * @license AGPL-3.0
14
 *
15
 * This code is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License, version 3,
17
 * as published by the Free Software Foundation.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License, version 3,
25
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
26
 *
27
 */
28
29
namespace OCA\Files_Sharing\External;
30
31
use OC\Files\Filesystem;
32
use OCA\FederatedFileSharing\DiscoveryManager;
33
use OCP\Files;
34
use OCP\Notification\IManager;
35
36
class Manager {
37
	const STORAGE = '\OCA\Files_Sharing\External\Storage';
38
39
	/**
40
	 * @var string
41
	 */
42
	private $uid;
43
44
	/**
45
	 * @var \OCP\IDBConnection
46
	 */
47
	private $connection;
48
49
	/**
50
	 * @var \OC\Files\Mount\Manager
51
	 */
52
	private $mountManager;
53
54
	/**
55
	 * @var \OCP\Files\Storage\IStorageFactory
56
	 */
57
	private $storageLoader;
58
59
	/**
60
	 * @var \OC\HTTPHelper
61
	 */
62
	private $httpHelper;
63
64
	/**
65
	 * @var IManager
66
	 */
67
	private $notificationManager;
68
	/** @var DiscoveryManager */
69
	private $discoveryManager;
70
71
	/**
72
	 * @param \OCP\IDBConnection $connection
73
	 * @param \OC\Files\Mount\Manager $mountManager
74
	 * @param \OCP\Files\Storage\IStorageFactory $storageLoader
75
	 * @param \OC\HTTPHelper $httpHelper
76
	 * @param IManager $notificationManager
77
	 * @param DiscoveryManager $discoveryManager
78
	 * @param string $uid
79
	 */
80
	public function __construct(\OCP\IDBConnection $connection,
81
								\OC\Files\Mount\Manager $mountManager,
82
								\OCP\Files\Storage\IStorageFactory $storageLoader,
83
								\OC\HTTPHelper $httpHelper,
84
								IManager $notificationManager,
85
								DiscoveryManager $discoveryManager,
86
								$uid) {
87
		$this->connection = $connection;
88
		$this->mountManager = $mountManager;
89
		$this->storageLoader = $storageLoader;
90
		$this->httpHelper = $httpHelper;
91
		$this->uid = $uid;
92
		$this->notificationManager = $notificationManager;
93
		$this->discoveryManager = $discoveryManager;
94
	}
95
96
	/**
97
	 * add new server-to-server share
98
	 *
99
	 * @param string $remote
100
	 * @param string $token
101
	 * @param string $password
102
	 * @param string $name
103
	 * @param string $owner
104
	 * @param boolean $accepted
105
	 * @param string $user
106
	 * @param int $remoteId
107
	 * @return Mount|null
108
	 */
109
	public function addShare($remote, $token, $password, $name, $owner, $accepted=false, $user = null, $remoteId = -1) {
110
111
		$user = $user ? $user : $this->uid;
112
		$accepted = $accepted ? 1 : 0;
113
		$name = Filesystem::normalizePath('/' . $name);
114
115
		if (!$accepted) {
116
			// To avoid conflicts with the mount point generation later,
117
			// we only use a temporary mount point name here. The real
118
			// mount point name will be generated when accepting the share,
119
			// using the original share item name.
120
			$tmpMountPointName = '{{TemporaryMountPointName#' . $name . '}}';
121
			$mountPoint = $tmpMountPointName;
122
			$hash = md5($tmpMountPointName);
123
			$data = [
124
				'remote'		=> $remote,
125
				'share_token'	=> $token,
126
				'password'		=> $password,
127
				'name'			=> $name,
128
				'owner'			=> $owner,
129
				'user'			=> $user,
130
				'mountpoint'	=> $mountPoint,
131
				'mountpoint_hash'	=> $hash,
132
				'accepted'		=> $accepted,
133
				'remote_id'		=> $remoteId,
134
			];
135
136
			$i = 1;
137
			while (!$this->connection->insertIfNotExist('*PREFIX*share_external', $data, ['user', 'mountpoint_hash'])) {
138
				// The external share already exists for the user
139
				$data['mountpoint'] = $tmpMountPointName . '-' . $i;
140
				$data['mountpoint_hash'] = md5($data['mountpoint']);
141
				$i++;
142
			}
143
			return null;
144
		}
145
146
		$mountPoint = Files::buildNotExistingFileName('/', $name);
147
		$mountPoint = Filesystem::normalizePath('/' . $mountPoint);
148
		$hash = md5($mountPoint);
149
150
		$query = $this->connection->prepare('
151
				INSERT INTO `*PREFIX*share_external`
152
					(`remote`, `share_token`, `password`, `name`, `owner`, `user`, `mountpoint`, `mountpoint_hash`, `accepted`, `remote_id`)
153
				VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
154
			');
155
		$query->execute(array($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId));
156
157
		$options = array(
158
			'remote'	=> $remote,
159
			'token'		=> $token,
160
			'password'	=> $password,
161
			'mountpoint'	=> $mountPoint,
162
			'owner'		=> $owner
163
		);
164
		return $this->mountShare($options);
165
	}
166
167
	/**
168
	 * get share
169
	 *
170
	 * @param int $id share id
171
	 * @return mixed share of false
172
	 */
173
	public function getShare($id) {
174
		$getShare = $this->connection->prepare('
175
			SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`
176
			FROM  `*PREFIX*share_external`
177
			WHERE `id` = ? AND `user` = ?');
178
		$result = $getShare->execute(array($id, $this->uid));
179
180
		return $result ? $getShare->fetch() : false;
181
	}
182
183
	/**
184
	 * accept server-to-server share
185
	 *
186
	 * @param int $id
187
	 * @return bool True if the share could be accepted, false otherwise
188
	 */
189
	public function acceptShare($id) {
190
191
		$share = $this->getShare($id);
192
193
		if ($share) {
194
			$mountPoint = Files::buildNotExistingFileName('/', $share['name']);
195
			$mountPoint = Filesystem::normalizePath('/' . $mountPoint);
196
			$hash = md5($mountPoint);
197
198
			$acceptShare = $this->connection->prepare('
199
				UPDATE `*PREFIX*share_external`
200
				SET `accepted` = ?,
201
					`mountpoint` = ?,
202
					`mountpoint_hash` = ?
203
				WHERE `id` = ? AND `user` = ?');
204
			$acceptShare->execute(array(1, $mountPoint, $hash, $id, $this->uid));
205
			$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'accept');
206
207
			\OC_Hook::emit('OCP\Share', 'federated_share_added', ['server' => $share['remote']]);
208
209
			$this->processNotification($id);
210
			return true;
211
		}
212
213
		return false;
214
	}
215
216
	/**
217
	 * decline server-to-server share
218
	 *
219
	 * @param int $id
220
	 * @return bool True if the share could be declined, false otherwise
221
	 */
222
	public function declineShare($id) {
223
224
		$share = $this->getShare($id);
225
226
		if ($share) {
227
			$removeShare = $this->connection->prepare('
228
				DELETE FROM `*PREFIX*share_external` WHERE `id` = ? AND `user` = ?');
229
			$removeShare->execute(array($id, $this->uid));
230
			$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
231
232
			$this->processNotification($id);
233
			return true;
234
		}
235
236
		return false;
237
	}
238
239
	/**
240
	 * @param int $remoteShare
241
	 */
242
	public function processNotification($remoteShare) {
243
		$filter = $this->notificationManager->createNotification();
244
		$filter->setApp('files_sharing')
245
			->setUser($this->uid)
246
			->setObject('remote_share', (int) $remoteShare);
247
		$this->notificationManager->markProcessed($filter);
248
	}
249
250
	/**
251
	 * inform remote server whether server-to-server share was accepted/declined
252
	 *
253
	 * @param string $remote
254
	 * @param string $token
255
	 * @param int $remoteId Share id on the remote host
256
	 * @param string $feedback
257
	 * @return boolean
258
	 */
259
	private function sendFeedbackToRemote($remote, $token, $remoteId, $feedback) {
260
261
		$url = rtrim($remote, '/') . $this->discoveryManager->getShareEndpoint($remote) . '/' . $remoteId . '/' . $feedback . '?format=' . \OCP\Share::RESPONSE_FORMAT;
262
		$fields = array('token' => $token);
263
264
		$result = $this->httpHelper->post($url, $fields);
265
		$status = json_decode($result['result'], true);
266
267
		return ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200));
268
	}
269
270
	/**
271
	 * remove '/user/files' from the path and trailing slashes
272
	 *
273
	 * @param string $path
274
	 * @return string
275
	 */
276
	protected function stripPath($path) {
277
		$prefix = '/' . $this->uid . '/files';
278
		return rtrim(substr($path, strlen($prefix)), '/');
279
	}
280
281
	public function getMount($data) {
282
		$data['manager'] = $this;
283
		$mountPoint = '/' . $this->uid . '/files' . $data['mountpoint'];
284
		$data['mountpoint'] = $mountPoint;
285
		$data['certificateManager'] = \OC::$server->getCertificateManager($this->uid);
286
		return new Mount(self::STORAGE, $mountPoint, $data, $this, $this->storageLoader);
0 ignored issues
show
Documentation introduced by
$this->storageLoader is of type object<OCP\Files\Storage\IStorageFactory>, but the function expects a object<OC\Files\Storage\StorageFactory>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
287
	}
288
289
	/**
290
	 * @param array $data
291
	 * @return Mount
292
	 */
293
	protected function mountShare($data) {
294
		$mount = $this->getMount($data);
295
		$this->mountManager->addMount($mount);
296
		return $mount;
297
	}
298
299
	/**
300
	 * @return \OC\Files\Mount\Manager
301
	 */
302
	public function getMountManager() {
303
		return $this->mountManager;
304
	}
305
306
	/**
307
	 * @param string $source
308
	 * @param string $target
309
	 * @return bool
310
	 */
311
	public function setMountPoint($source, $target) {
312
		$source = $this->stripPath($source);
313
		$target = $this->stripPath($target);
314
		$sourceHash = md5($source);
315
		$targetHash = md5($target);
316
317
		$query = $this->connection->prepare('
318
			UPDATE `*PREFIX*share_external`
319
			SET `mountpoint` = ?, `mountpoint_hash` = ?
320
			WHERE `mountpoint_hash` = ?
321
			AND `user` = ?
322
		');
323
		$result = (bool)$query->execute(array($target, $targetHash, $sourceHash, $this->uid));
324
325
		return $result;
326
	}
327
328
	public function removeShare($mountPoint) {
329
330
		$mountPointObj = $this->mountManager->find($mountPoint);
331
		$id = $mountPointObj->getStorage()->getCache()->getId('');
332
333
		$mountPoint = $this->stripPath($mountPoint);
334
		$hash = md5($mountPoint);
335
336
		$getShare = $this->connection->prepare('
337
			SELECT `remote`, `share_token`, `remote_id`
338
			FROM  `*PREFIX*share_external`
339
			WHERE `mountpoint_hash` = ? AND `user` = ?');
340
		$result = $getShare->execute(array($hash, $this->uid));
341
342 View Code Duplication
		if ($result) {
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...
343
			$share = $getShare->fetch();
344
			$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
345
		}
346
		$getShare->closeCursor();
347
348
		$query = $this->connection->prepare('
349
			DELETE FROM `*PREFIX*share_external`
350
			WHERE `mountpoint_hash` = ?
351
			AND `user` = ?
352
		');
353
		$result = (bool)$query->execute(array($hash, $this->uid));
354
355
		if($result) {
356
			$this->removeReShares($id);
357
		}
358
359
		return $result;
360
	}
361
362
	/**
363
	 * remove re-shares from share table and mapping in the federated_reshares table
364
	 * 
365
	 * @param $mountPointId
366
	 */
367
	protected function removeReShares($mountPointId) {
368
		$selectQuery = $this->connection->getQueryBuilder();
369
		$query = $this->connection->getQueryBuilder();
370
		$selectQuery->select('id')->from('share')
371
			->where($selectQuery->expr()->eq('file_source', $query->createNamedParameter($mountPointId)));
372
		$select = $selectQuery->getSQL();
373
374
375
		$query->delete('federated_reshares')
376
			->where($query->expr()->in('share_id', $query->createFunction('(' . $select . ')')));
377
		$query->execute();
378
379
		$deleteReShares = $this->connection->getQueryBuilder();
380
		$deleteReShares->delete('share')
381
			->where($deleteReShares->expr()->eq('file_source', $deleteReShares->createNamedParameter($mountPointId)));
382
		$deleteReShares->execute();
383
	}
384
385
	/**
386
	 * remove all shares for user $uid if the user was deleted
387
	 *
388
	 * @param string $uid
389
	 * @return bool
390
	 */
391
	public function removeUserShares($uid) {
392
		$getShare = $this->connection->prepare('
393
			SELECT `remote`, `share_token`, `remote_id`
394
			FROM  `*PREFIX*share_external`
395
			WHERE `user` = ?');
396
		$result = $getShare->execute(array($uid));
397
398 View Code Duplication
		if ($result) {
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...
399
			$shares = $getShare->fetchAll();
400
			foreach($shares as $share) {
401
				$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
402
			}
403
		}
404
405
		$query = $this->connection->prepare('
406
			DELETE FROM `*PREFIX*share_external`
407
			WHERE `user` = ?
408
		');
409
		return (bool)$query->execute(array($uid));
410
	}
411
412
	/**
413
	 * return a list of shares which are not yet accepted by the user
414
	 *
415
	 * @return array list of open server-to-server shares
416
	 */
417
	public function getOpenShares() {
418
		return $this->getShares(false);
419
	}
420
421
	/**
422
	 * return a list of shares which are accepted by the user
423
	 *
424
	 * @return array list of accepted server-to-server shares
425
	 */
426
	public function getAcceptedShares() {
427
		return $this->getShares(true);
428
	}
429
430
	/**
431
	 * return a list of shares for the user
432
	 *
433
	 * @param bool|null $accepted True for accepted only,
434
	 *                            false for not accepted,
435
	 *                            null for all shares of the user
436
	 * @return array list of open server-to-server shares
437
	 */
438
	private function getShares($accepted) {
439
		$query = 'SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`
440
		          FROM `*PREFIX*share_external` 
441
				  WHERE `user` = ?';
442
		$parameters = [$this->uid];
443
		if (!is_null($accepted)) {
444
			$query .= ' AND `accepted` = ?';
445
			$parameters[] = (int) $accepted;
446
		}
447
		$query .= ' ORDER BY `id` ASC';
448
449
		$shares = $this->connection->prepare($query);
450
		$result = $shares->execute($parameters);
451
452
		return $result ? $shares->fetchAll() : [];
453
	}
454
}
455