Completed
Push — master ( b4df57...e6895c )
by Lukas
26s
created

Storage   F

Complexity

Total Complexity 54

Size/Duplication

Total Lines 311
Duplicated Lines 2.89 %

Coupling/Cohesion

Components 4
Dependencies 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 9
loc 311
rs 2.6679
wmc 54
lcom 4
cbo 20

20 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 31 2
A getWatcher() 0 10 3
A getRemoteUser() 0 3 1
A getRemote() 0 3 1
A getMountPoint() 0 3 1
A getToken() 0 3 1
A getPassword() 0 3 1
A getId() 0 3 1
A getCache() 0 6 2
A getScanner() 9 9 3
A hasUpdated() 0 19 4
C checkStorageAvailability() 0 30 7
A file_exists() 0 7 2
A testRemote() 0 9 4
B testRemoteUrl() 0 20 5
A remoteIsOwnCloud() 0 6 3
B getShareInfo() 0 31 6
A getOwner() 0 4 1
A isSharable() 0 6 3
A getPermissions() 0 15 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Storage 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Storage, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @author Björn Schießle <[email protected]>
4
 * @author Lukas Reschke <[email protected]>
5
 * @author Morris Jobke <[email protected]>
6
 * @author Robin Appelman <[email protected]>
7
 * @author Thomas Müller <[email protected]>
8
 * @author Vincent Petry <[email protected]>
9
 *
10
 * @copyright Copyright (c) 2016, ownCloud, Inc.
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\Files_Sharing\External;
28
29
use GuzzleHttp\Exception\ClientException;
30
use GuzzleHttp\Exception\ConnectException;
31
use OC\Files\Storage\DAV;
32
use OC\ForbiddenException;
33
use OCA\FederatedFileSharing\DiscoveryManager;
34
use OCA\Files_Sharing\ISharedStorage;
35
use OCP\Files\NotFoundException;
36
use OCP\Files\StorageInvalidException;
37
use OCP\Files\StorageNotAvailableException;
38
39
class Storage extends DAV implements ISharedStorage {
40
	/** @var string */
41
	private $remoteUser;
42
	/** @var string */
43
	private $remote;
44
	/** @var string */
45
	private $mountPoint;
46
	/** @var string */
47
	private $token;
48
	/** @var \OCP\ICacheFactory */
49
	private $memcacheFactory;
50
	/** @var \OCP\Http\Client\IClientService */
51
	private $httpClient;
52
	/** @var \OCP\ICertificateManager */
53
	private $certificateManager;
54
	/** @var bool */
55
	private $updateChecked = false;
56
57
	/**
58
	 * @var \OCA\Files_Sharing\External\Manager
59
	 */
60
	private $manager;
61
62
	public function __construct($options) {
63
		$this->memcacheFactory = \OC::$server->getMemCacheFactory();
64
		$this->httpClient = \OC::$server->getHTTPClientService();
65
		$discoveryManager = new DiscoveryManager(
66
			$this->memcacheFactory,
67
			\OC::$server->getHTTPClientService()
68
		);
69
70
		$this->manager = $options['manager'];
71
		$this->certificateManager = $options['certificateManager'];
72
		$this->remote = $options['remote'];
73
		$this->remoteUser = $options['owner'];
74
		list($protocol, $remote) = explode('://', $this->remote);
75
		if (strpos($remote, '/')) {
76
			list($host, $root) = explode('/', $remote, 2);
77
		} else {
78
			$host = $remote;
79
			$root = '';
80
		}
81
		$secure = $protocol === 'https';
82
		$root = rtrim($root, '/') . $discoveryManager->getWebDavEndpoint($this->remote);
83
		$this->mountPoint = $options['mountpoint'];
84
		$this->token = $options['token'];
85
		parent::__construct(array(
86
			'secure' => $secure,
87
			'host' => $host,
88
			'root' => $root,
89
			'user' => $options['token'],
90
			'password' => (string)$options['password']
91
		));
92
	}
93
94
	public function getWatcher($path = '', $storage = null) {
95
		if (!$storage) {
96
			$storage = $this;
97
		}
98
		if (!isset($this->watcher)) {
99
			$this->watcher = new Watcher($storage);
100
			$this->watcher->setPolicy(\OC\Files\Cache\Watcher::CHECK_ONCE);
101
		}
102
		return $this->watcher;
103
	}
104
105
	public function getRemoteUser() {
106
		return $this->remoteUser;
107
	}
108
109
	public function getRemote() {
110
		return $this->remote;
111
	}
112
113
	public function getMountPoint() {
114
		return $this->mountPoint;
115
	}
116
117
	public function getToken() {
118
		return $this->token;
119
	}
120
121
	public function getPassword() {
122
		return $this->password;
123
	}
124
125
	/**
126
	 * @brief get id of the mount point
127
	 * @return string
128
	 */
129
	public function getId() {
130
		return 'shared::' . md5($this->token . '@' . $this->remote);
131
	}
132
133
	public function getCache($path = '', $storage = null) {
134
		if (is_null($this->cache)) {
135
			$this->cache = new Cache($this, $this->remote, $this->remoteUser);
136
		}
137
		return $this->cache;
138
	}
139
140
	/**
141
	 * @param string $path
142
	 * @param \OC\Files\Storage\Storage $storage
143
	 * @return \OCA\Files_Sharing\External\Scanner
144
	 */
145 View Code Duplication
	public function getScanner($path = '', $storage = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
146
		if (!$storage) {
147
			$storage = $this;
148
		}
149
		if (!isset($this->scanner)) {
150
			$this->scanner = new Scanner($storage);
151
		}
152
		return $this->scanner;
153
	}
154
155
	/**
156
	 * check if a file or folder has been updated since $time
157
	 *
158
	 * @param string $path
159
	 * @param int $time
160
	 * @throws \OCP\Files\StorageNotAvailableException
161
	 * @throws \OCP\Files\StorageInvalidException
162
	 * @return bool
163
	 */
164
	public function hasUpdated($path, $time) {
165
		// since for owncloud webdav servers we can rely on etag propagation we only need to check the root of the storage
166
		// because of that we only do one check for the entire storage per request
167
		if ($this->updateChecked) {
168
			return false;
169
		}
170
		$this->updateChecked = true;
171
		try {
172
			return parent::hasUpdated('', $time);
173
		} catch (StorageInvalidException $e) {
174
			// check if it needs to be removed
175
			$this->checkStorageAvailability();
176
			throw $e;
177
		} catch (StorageNotAvailableException $e) {
178
			// check if it needs to be removed or just temp unavailable
179
			$this->checkStorageAvailability();
180
			throw $e;
181
		}
182
	}
183
184
	/**
185
	 * Check whether this storage is permanently or temporarily
186
	 * unavailable
187
	 *
188
	 * @throws \OCP\Files\StorageNotAvailableException
189
	 * @throws \OCP\Files\StorageInvalidException
190
	 */
191
	public function checkStorageAvailability() {
192
		// see if we can find out why the share is unavailable
193
		try {
194
			$this->getShareInfo();
195
		} catch (NotFoundException $e) {
196
			// a 404 can either mean that the share no longer exists or there is no ownCloud on the remote
197
			if ($this->testRemote()) {
198
				// valid ownCloud instance means that the public share no longer exists
199
				// since this is permanent (re-sharing the file will create a new token)
200
				// we remove the invalid storage
201
				$this->manager->removeShare($this->mountPoint);
202
				$this->manager->getMountManager()->removeMount($this->mountPoint);
203
				throw new StorageInvalidException();
204
			} else {
205
				// ownCloud instance is gone, likely to be a temporary server configuration error
206
				throw new StorageNotAvailableException();
207
			}
208
		} catch (ForbiddenException $e) {
209
			// auth error, remove share for now (provide a dialog in the future)
210
			$this->manager->removeShare($this->mountPoint);
211
			$this->manager->getMountManager()->removeMount($this->mountPoint);
212
			throw new StorageInvalidException();
213
		} catch (\GuzzleHttp\Exception\ConnectException $e) {
0 ignored issues
show
Bug introduced by
The class GuzzleHttp\Exception\ConnectException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
214
			throw new StorageNotAvailableException();
215
		} catch (\GuzzleHttp\Exception\RequestException $e) {
0 ignored issues
show
Bug introduced by
The class GuzzleHttp\Exception\RequestException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
216
			throw new StorageNotAvailableException();
217
		} catch (\Exception $e) {
218
			throw $e;
219
		}
220
	}
221
222
	public function file_exists($path) {
223
		if ($path === '') {
224
			return true;
225
		} else {
226
			return parent::file_exists($path);
227
		}
228
	}
229
230
	/**
231
	 * check if the configured remote is a valid federated share provider
232
	 *
233
	 * @return bool
234
	 */
235
	protected function testRemote() {
236
		try {
237
			return $this->testRemoteUrl($this->remote . '/ocs-provider/index.php')
238
				|| $this->testRemoteUrl($this->remote . '/ocs-provider/')
239
				|| $this->testRemoteUrl($this->remote . '/status.php');
240
		} catch (\Exception $e) {
241
			return false;
242
		}
243
	}
244
245
	/**
246
	 * @param string $url
247
	 * @return bool
248
	 */
249
	private function testRemoteUrl($url) {
250
		$cache = $this->memcacheFactory->create('files_sharing_remote_url');
251
		if($cache->hasKey($url)) {
0 ignored issues
show
Deprecated Code introduced by
The method OCP\ICache::hasKey() has been deprecated with message: 9.1.0 Directly read from GET to prevent race conditions

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
252
			return (bool)$cache->get($url);
253
		}
254
255
		$client = $this->httpClient->newClient();
256
		try {
257
			$result = $client->get($url)->getBody();
258
			$data = json_decode($result);
259
			$returnValue = (is_object($data) && !empty($data->version));
260
		} catch (ConnectException $e) {
0 ignored issues
show
Bug introduced by
The class GuzzleHttp\Exception\ConnectException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
261
			$returnValue = false;
262
		} catch (ClientException $e) {
0 ignored issues
show
Bug introduced by
The class GuzzleHttp\Exception\ClientException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
263
			$returnValue = false;
264
		}
265
266
		$cache->set($url, $returnValue);
267
		return $returnValue;
268
	}
269
270
	/**
271
	 * Whether the remote is an ownCloud, used since some sharing features are not
272
	 * standardized. Let's use this to detect whether to use it.
273
	 *
274
	 * @return bool
275
	 */
276
	public function remoteIsOwnCloud() {
277
		if(defined('PHPUNIT_RUN') || !$this->testRemoteUrl($this->getRemote() . '/status.php')) {
278
			return false;
279
		}
280
		return true;
281
	}
282
283
	/**
284
	 * @return mixed
285
	 * @throws ForbiddenException
286
	 * @throws NotFoundException
287
	 * @throws \Exception
288
	 */
289
	public function getShareInfo() {
290
		$remote = $this->getRemote();
291
		$token = $this->getToken();
292
		$password = $this->getPassword();
293
294
		// If remote is not an ownCloud do not try to get any share info
295
		if(!$this->remoteIsOwnCloud()) {
296
			return ['status' => 'unsupported'];
297
		}
298
299
		$url = rtrim($remote, '/') . '/index.php/apps/files_sharing/shareinfo?t=' . $token;
300
301
		// TODO: DI
302
		$client = \OC::$server->getHTTPClientService()->newClient();
303
		try {
304
			$response = $client->post($url, ['body' => ['password' => $password]]);
305
		} catch (\GuzzleHttp\Exception\RequestException $e) {
0 ignored issues
show
Bug introduced by
The class GuzzleHttp\Exception\RequestException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
306
			if ($e->getCode() === 401 || $e->getCode() === 403) {
307
				throw new ForbiddenException();
308
			}
309
			if ($e->getCode() === 404) {
310
				throw new NotFoundException();
311
			}
312
			// throw this to be on the safe side: the share will still be visible
313
			// in the UI in case the failure is intermittent, and the user will
314
			// be able to decide whether to remove it if it's really gone
315
			throw new StorageNotAvailableException();
316
		}
317
318
		return json_decode($response->getBody(), true);
319
	}
320
321
	public function getOwner($path) {
322
		list(, $remote) = explode('://', $this->remote, 2);
323
		return $this->remoteUser . '@' . $remote;
324
	}
325
326
	public function isSharable($path) {
327
		if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) {
0 ignored issues
show
Deprecated Code introduced by
The method OCP\Util::isSharingDisabledForUser() has been deprecated with message: 9.1.0 Use \OC::$server->getShareManager()->sharingDisabledForUser

This method 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 method will be removed from the class and what other method or class to use instead.

Loading history...
328
			return false;
329
		}
330
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_SHARE);
331
	}
332
	
333
	public function getPermissions($path) {
334
		$response = $this->propfind($path);
335
		if (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) {
336
			$permissions = $response['{http://open-collaboration-services.org/ns}share-permissions'];
337
		} else {
338
			// use default permission if remote server doesn't provide the share permissions
339
			if ($this->is_dir($path)) {
340
				$permissions = \OCP\Constants::PERMISSION_ALL;
341
			} else {
342
				$permissions = \OCP\Constants::PERMISSION_ALL & ~\OCP\Constants::PERMISSION_CREATE;
343
			}
344
		}
345
346
		return $permissions;
347
	}
348
349
}
350