Passed
Push — master ( c96044...9809b7 )
by Roeland
10:53 queued 10s
created

SharedStorage::changeLock()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 3
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bart Visscher <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Michael Gapczynski <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Robin Appelman <[email protected]>
12
 * @author Robin McCorkell <[email protected]>
13
 * @author Roeland Jago Douma <[email protected]>
14
 * @author scambra <[email protected]>
15
 * @author Thomas Müller <[email protected]>
16
 * @author Vincent Petry <[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;
35
36
use OC\Files\Cache\FailedCache;
37
use OC\Files\Filesystem;
38
use OC\Files\Storage\FailedStorage;
39
use OC\Files\Storage\Wrapper\PermissionsMask;
40
use OC\User\NoUserException;
41
use OCP\Constants;
42
use OCP\Files\Cache\ICacheEntry;
43
use OCP\Files\NotFoundException;
44
use OCP\Files\Storage\IDisableEncryptionStorage;
45
use OCP\Files\Storage\IStorage;
46
use OCP\Lock\ILockingProvider;
47
48
/**
49
 * Convert target path to source path and pass the function call to the correct storage provider
50
 */
51
class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage, IDisableEncryptionStorage {
52
53
	/** @var \OCP\Share\IShare */
54
	private $superShare;
55
56
	/** @var \OCP\Share\IShare[] */
57
	private $groupedShares;
58
59
	/**
60
	 * @var \OC\Files\View
61
	 */
62
	private $ownerView;
63
64
	private $initialized = false;
65
66
	/**
67
	 * @var ICacheEntry
68
	 */
69
	private $sourceRootInfo;
70
71
	/** @var string */
72
	private $user;
73
74
	/**
75
	 * @var \OCP\ILogger
76
	 */
77
	private $logger;
78
79
	/** @var  IStorage */
80
	private $nonMaskedStorage;
81
82
	private $options;
0 ignored issues
show
introduced by
The private property $options is not used, and could be removed.
Loading history...
83
84
	/** @var boolean */
85
	private $sharingDisabledForUser;
86
87
	public function __construct($arguments) {
88
		$this->ownerView = $arguments['ownerView'];
89
		$this->logger = \OC::$server->getLogger();
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getLogger() has been deprecated. ( Ignorable by Annotation )

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

89
		$this->logger = /** @scrutinizer ignore-deprecated */ \OC::$server->getLogger();
Loading history...
90
91
		$this->superShare = $arguments['superShare'];
92
		$this->groupedShares = $arguments['groupedShares'];
93
94
		$this->user = $arguments['user'];
95
		if (isset($arguments['sharingDisabledForUser'])) {
96
			$this->sharingDisabledForUser = $arguments['sharingDisabledForUser'];
97
		} else {
98
			$this->sharingDisabledForUser = false;
99
		}
100
101
		parent::__construct([
102
			'storage' => null,
103
			'root' => null,
104
		]);
105
	}
106
107
	/**
108
	 * @return ICacheEntry
109
	 */
110
	private function getSourceRootInfo() {
111
		if (is_null($this->sourceRootInfo)) {
112
			if (is_null($this->superShare->getNodeCacheEntry())) {
113
				$this->init();
114
				$this->sourceRootInfo = $this->nonMaskedStorage->getCache()->get($this->rootPath);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->nonMaskedStorage-...)->get($this->rootPath) can also be of type false. However, the property $sourceRootInfo is declared as type OCP\Files\Cache\ICacheEntry. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
115
			} else {
116
				$this->sourceRootInfo = $this->superShare->getNodeCacheEntry();
117
			}
118
		}
119
		return $this->sourceRootInfo;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->sourceRootInfo could also return false which is incompatible with the documented return type OCP\Files\Cache\ICacheEntry. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
120
	}
121
122
	private function init() {
123
		if ($this->initialized) {
124
			return;
125
		}
126
		$this->initialized = true;
127
		try {
128
			Filesystem::initMountPoints($this->superShare->getShareOwner());
129
			$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
130
			list($this->nonMaskedStorage, $this->rootPath) = $this->ownerView->resolvePath($sourcePath);
131
			$this->storage = new PermissionsMask([
132
				'storage' => $this->nonMaskedStorage,
133
				'mask' => $this->superShare->getPermissions()
134
			]);
135
		} catch (NotFoundException $e) {
136
			// original file not accessible or deleted, set FailedStorage
137
			$this->storage = new FailedStorage(['exception' => $e]);
138
			$this->cache = new FailedCache();
139
			$this->rootPath = '';
140
		} catch (NoUserException $e) {
141
			// sharer user deleted, set FailedStorage
142
			$this->storage = new FailedStorage(['exception' => $e]);
143
			$this->cache = new FailedCache();
144
			$this->rootPath = '';
145
		} catch (\Exception $e) {
146
			$this->storage = new FailedStorage(['exception' => $e]);
147
			$this->cache = new FailedCache();
148
			$this->rootPath = '';
149
			$this->logger->logException($e);
150
		}
151
152
		if (!$this->nonMaskedStorage) {
153
			$this->nonMaskedStorage = $this->storage;
154
		}
155
	}
156
157
	/**
158
	 * @inheritdoc
159
	 */
160
	public function instanceOfStorage($class) {
161
		if ($class === '\OC\Files\Storage\Common') {
162
			return true;
163
		}
164
		if (in_array($class, ['\OC\Files\Storage\Home', '\OC\Files\ObjectStore\HomeObjectStoreStorage'])) {
165
			return false;
166
		}
167
		return parent::instanceOfStorage($class);
168
	}
169
170
	/**
171
	 * @return string
172
	 */
173
	public function getShareId() {
174
		return $this->superShare->getId();
175
	}
176
177
	private function isValid() {
178
		return $this->getSourceRootInfo() && ($this->getSourceRootInfo()->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE;
179
	}
180
181
	/**
182
	 * get id of the mount point
183
	 *
184
	 * @return string
185
	 */
186
	public function getId() {
187
		return 'shared::' . $this->getMountPoint();
188
	}
189
190
	/**
191
	 * Get the permissions granted for a shared file
192
	 *
193
	 * @param string $target Shared target file path
194
	 * @return int CRUDS permissions granted
195
	 */
196
	public function getPermissions($target = '') {
197
		if (!$this->isValid()) {
198
			return 0;
199
		}
200
		$permissions = parent::getPermissions($target) & $this->superShare->getPermissions();
201
202
		// part files and the mount point always have delete permissions
203
		if ($target === '' || pathinfo($target, PATHINFO_EXTENSION) === 'part') {
204
			$permissions |= \OCP\Constants::PERMISSION_DELETE;
205
		}
206
207
		if ($this->sharingDisabledForUser) {
208
			$permissions &= ~\OCP\Constants::PERMISSION_SHARE;
209
		}
210
211
		return $permissions;
212
	}
213
214
	public function isCreatable($path) {
215
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_CREATE);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getPermiss...ants::PERMISSION_CREATE returns the type integer which is incompatible with the return type mandated by OCP\Files\Storage::isCreatable() of boolean.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
216
	}
217
218
	public function isReadable($path) {
219
		if (!$this->isValid()) {
220
			return false;
221
		}
222
		if (!$this->file_exists($path)) {
223
			return false;
224
		}
225
		/** @var IStorage $storage */
226
		/** @var string $internalPath */
227
		list($storage, $internalPath) = $this->resolvePath($path);
228
		return $storage->isReadable($internalPath);
229
	}
230
231
	public function isUpdatable($path) {
232
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_UPDATE);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getPermiss...ants::PERMISSION_UPDATE returns the type integer which is incompatible with the return type mandated by OCP\Files\Storage::isUpdatable() of boolean.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
233
	}
234
235
	public function isDeletable($path) {
236
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_DELETE);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getPermiss...ants::PERMISSION_DELETE returns the type integer which is incompatible with the return type mandated by OCP\Files\Storage::isDeletable() of boolean.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
237
	}
238
239
	public function isSharable($path) {
240
		if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) {
241
			return false;
242
		}
243
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_SHARE);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getPermiss...tants::PERMISSION_SHARE returns the type integer which is incompatible with the return type mandated by OCP\Files\Storage::isSharable() of boolean.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
244
	}
245
246
	public function fopen($path, $mode) {
247
		if ($source = $this->getUnjailedPath($path)) {
248
			switch ($mode) {
249
				case 'r+':
250
				case 'rb+':
251
				case 'w+':
252
				case 'wb+':
253
				case 'x+':
254
				case 'xb+':
255
				case 'a+':
256
				case 'ab+':
257
				case 'w':
258
				case 'wb':
259
				case 'x':
260
				case 'xb':
261
				case 'a':
262
				case 'ab':
263
					$creatable = $this->isCreatable(dirname($path));
264
					$updatable = $this->isUpdatable($path);
265
					// if neither permissions given, no need to continue
266
					if (!$creatable && !$updatable) {
267
						if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
268
							$updatable = $this->isUpdatable(dirname($path));
269
						}
270
271
						if (!$updatable) {
272
							return false;
273
						}
274
					}
275
276
					$exists = $this->file_exists($path);
277
					// if a file exists, updatable permissions are required
278
					if ($exists && !$updatable) {
279
						return false;
280
					}
281
282
					// part file is allowed if !$creatable but the final file is $updatable
283
					if (pathinfo($path, PATHINFO_EXTENSION) !== 'part') {
284
						if (!$exists && !$creatable) {
285
							return false;
286
						}
287
					}
288
			}
289
			$info = [
290
				'target' => $this->getMountPoint() . $path,
291
				'source' => $source,
292
				'mode' => $mode,
293
			];
294
			\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
295
			return $this->nonMaskedStorage->fopen($this->getUnjailedPath($path), $mode);
296
		}
297
		return false;
298
	}
299
300
	/**
301
	 * see http://php.net/manual/en/function.rename.php
302
	 *
303
	 * @param string $path1
304
	 * @param string $path2
305
	 * @return bool
306
	 */
307
	public function rename($path1, $path2) {
308
		$this->init();
309
		$isPartFile = pathinfo($path1, PATHINFO_EXTENSION) === 'part';
310
		$targetExists = $this->file_exists($path2);
311
		$sameFodler = dirname($path1) === dirname($path2);
312
313
		if ($targetExists || ($sameFodler && !$isPartFile)) {
314
			if (!$this->isUpdatable('')) {
315
				return false;
316
			}
317
		} else {
318
			if (!$this->isCreatable('')) {
319
				return false;
320
			}
321
		}
322
323
		return $this->nonMaskedStorage->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
324
	}
325
326
	/**
327
	 * return mount point of share, relative to data/user/files
328
	 *
329
	 * @return string
330
	 */
331
	public function getMountPoint() {
332
		return $this->superShare->getTarget();
333
	}
334
335
	/**
336
	 * @param string $path
337
	 */
338
	public function setMountPoint($path) {
339
		$this->superShare->setTarget($path);
340
341
		foreach ($this->groupedShares as $share) {
342
			$share->setTarget($path);
343
		}
344
	}
345
346
	/**
347
	 * get the user who shared the file
348
	 *
349
	 * @return string
350
	 */
351
	public function getSharedFrom() {
352
		return $this->superShare->getShareOwner();
353
	}
354
355
	/**
356
	 * @return \OCP\Share\IShare
357
	 */
358
	public function getShare() {
359
		return $this->superShare;
360
	}
361
362
	/**
363
	 * return share type, can be "file" or "folder"
364
	 *
365
	 * @return string
366
	 */
367
	public function getItemType() {
368
		return $this->superShare->getNodeType();
369
	}
370
371
	/**
372
	 * @param string $path
373
	 * @param null $storage
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $storage is correct as it would always require null to be passed?
Loading history...
374
	 * @return Cache
375
	 */
376
	public function getCache($path = '', $storage = null) {
377
		if ($this->cache) {
378
			return $this->cache;
379
		}
380
		if (!$storage) {
0 ignored issues
show
introduced by
$storage is of type null, thus it always evaluated to false.
Loading history...
381
			$storage = $this;
382
		}
383
		$sourceRoot  = $this->getSourceRootInfo();
384
		if ($this->storage instanceof FailedStorage) {
385
			return new FailedCache();
0 ignored issues
show
Bug Best Practice introduced by
The expression return new OC\Files\Cache\FailedCache() returns the type OC\Files\Cache\FailedCache which is incompatible with the documented return type OCA\Files_Sharing\Cache.
Loading history...
386
		}
387
388
		$this->cache = new \OCA\Files_Sharing\Cache($storage, $sourceRoot, $this->superShare);
0 ignored issues
show
Unused Code introduced by
The call to OCA\Files_Sharing\Cache::__construct() has too many arguments starting with $this->superShare. ( Ignorable by Annotation )

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

388
		$this->cache = /** @scrutinizer ignore-call */ new \OCA\Files_Sharing\Cache($storage, $sourceRoot, $this->superShare);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
389
		return $this->cache;
390
	}
391
392
	public function getScanner($path = '', $storage = null) {
393
		if (!$storage) {
394
			$storage = $this;
395
		}
396
		return new \OCA\Files_Sharing\Scanner($storage);
397
	}
398
399
	public function getOwner($path) {
400
		return $this->superShare->getShareOwner();
401
	}
402
403
	/**
404
	 * unshare complete storage, also the grouped shares
405
	 *
406
	 * @return bool
407
	 */
408
	public function unshareStorage() {
409
		foreach ($this->groupedShares as $share) {
410
			\OC::$server->getShareManager()->deleteFromSelf($share, $this->user);
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getShareManager() has been deprecated. ( Ignorable by Annotation )

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

410
			/** @scrutinizer ignore-deprecated */ \OC::$server->getShareManager()->deleteFromSelf($share, $this->user);
Loading history...
411
		}
412
		return true;
413
	}
414
415
	/**
416
	 * @param string $path
417
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
418
	 * @param \OCP\Lock\ILockingProvider $provider
419
	 * @throws \OCP\Lock\LockedException
420
	 */
421
	public function acquireLock($path, $type, ILockingProvider $provider) {
422
		/** @var \OCP\Files\Storage $targetStorage */
423
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
424
		$targetStorage->acquireLock($targetInternalPath, $type, $provider);
425
		// lock the parent folders of the owner when locking the share as recipient
426
		if ($path === '') {
427
			$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
428
			$this->ownerView->lockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
429
		}
430
	}
431
432
	/**
433
	 * @param string $path
434
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
435
	 * @param \OCP\Lock\ILockingProvider $provider
436
	 */
437
	public function releaseLock($path, $type, ILockingProvider $provider) {
438
		/** @var \OCP\Files\Storage $targetStorage */
439
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
440
		$targetStorage->releaseLock($targetInternalPath, $type, $provider);
441
		// unlock the parent folders of the owner when unlocking the share as recipient
442
		if ($path === '') {
443
			$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
444
			$this->ownerView->unlockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
445
		}
446
	}
447
448
	/**
449
	 * @param string $path
450
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
451
	 * @param \OCP\Lock\ILockingProvider $provider
452
	 */
453
	public function changeLock($path, $type, ILockingProvider $provider) {
454
		/** @var \OCP\Files\Storage $targetStorage */
455
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
456
		$targetStorage->changeLock($targetInternalPath, $type, $provider);
457
	}
458
459
	/**
460
	 * @return array [ available, last_checked ]
461
	 */
462
	public function getAvailability() {
463
		// shares do not participate in availability logic
464
		return [
465
			'available' => true,
466
			'last_checked' => 0
467
		];
468
	}
469
470
	/**
471
	 * @param bool $available
472
	 */
473
	public function setAvailability($available) {
474
		// shares do not participate in availability logic
475
	}
476
477
	public function getSourceStorage() {
478
		$this->init();
479
		return $this->nonMaskedStorage;
480
	}
481
482
	public function getWrapperStorage() {
483
		$this->init();
484
		return $this->storage;
485
	}
486
487
	public function file_get_contents($path) {
488
		$info = [
489
			'target' => $this->getMountPoint() . '/' . $path,
490
			'source' => $this->getUnjailedPath($path),
491
		];
492
		\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info);
493
		return parent::file_get_contents($path);
494
	}
495
496
	public function file_put_contents($path, $data) {
497
		$info = [
498
			'target' => $this->getMountPoint() . '/' . $path,
499
			'source' => $this->getUnjailedPath($path),
500
		];
501
		\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info);
502
		return parent::file_put_contents($path, $data);
503
	}
504
505
	public function setMountOptions(array $options) {
506
		$this->mountOptions = $options;
0 ignored issues
show
Bug Best Practice introduced by
The property mountOptions does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
507
	}
508
509
	public function getUnjailedPath($path) {
510
		$this->init();
511
		return parent::getUnjailedPath($path);
512
	}
513
}
514