Completed
Push — master ( 24957e...d8c031 )
by Thomas
16:29 queued 07:32
created

Storage::test()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 0
dl 0
loc 13
rs 9.4285
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 Lukas Reschke <[email protected]>
6
 * @author Morris Jobke <[email protected]>
7
 * @author Robin Appelman <[email protected]>
8
 * @author Thomas Müller <[email protected]>
9
 * @author Vincent Petry <[email protected]>
10
 *
11
 * @copyright Copyright (c) 2017, ownCloud GmbH
12
 * @license AGPL-3.0
13
 *
14
 * This code is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License, version 3,
16
 * as published by the Free Software Foundation.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
 * GNU Affero General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU Affero General Public License, version 3,
24
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
25
 *
26
 */
27
28
namespace OCA\Files_Sharing\External;
29
30
use GuzzleHttp\Exception\ClientException;
31
use GuzzleHttp\Exception\ConnectException;
32
use OC\Files\Storage\DAV;
33
use OCA\FederatedFileSharing\DiscoveryManager;
34
use OCA\Files_Sharing\ISharedStorage;
35
use OCP\Files\StorageInvalidException;
36
use OCP\Files\StorageNotAvailableException;
37
use Sabre\DAV\Client;
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
		$this->manager = $options['manager'];
66
		$this->certificateManager = $options['certificateManager'];
67
		$this->remote = $options['remote'];
68
		$this->remoteUser = $options['owner'];
69
		list($protocol, $remote) = explode('://', $this->remote);
70
		if (strpos($remote, '/')) {
71
			list($host, $root) = explode('/', $remote, 2);
72
		} else {
73
			$host = $remote;
74
			$root = '';
75
		}
76
		$secure = $protocol === 'https';
77
		$this->mountPoint = $options['mountpoint'];
78
		$this->token = $options['token'];
79
		parent::__construct([
80
			'secure' => $secure,
81
			'host' => $host,
82
			// root will be adjusted lazily in init() with discovery manager
83
			'root' => $root,
84
			'user' => $options['token'],
85
			'password' => (string)$options['password'],
86
			// Federated sharing always uses BASIC auth
87
			'authType' => Client::AUTH_BASIC
88
		]);
89
	}
90
91
	protected function init() {
92
		if ($this->ready) {
93
			return;
94
		}
95
		$discoveryManager = new DiscoveryManager(
96
			$this->memcacheFactory,
97
			\OC::$server->getHTTPClientService()
98
		);
99
100
		$this->root = rtrim($this->root, '/') . $discoveryManager->getWebDavEndpoint($this->remote);
101 View Code Duplication
		if (!$this->root || $this->root[0] !== '/') {
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...
102
			$this->root = '/' . $this->root;
103
		}
104
		if (substr($this->root, -1, 1) !== '/') {
105
			$this->root .= '/';
106
		}
107
		parent::init();
108
	}
109
110
	/** {@inheritdoc} */
111
	public function createBaseUri() {
112
		// require lazy-initializing root to return correct value
113
		$this->init();
114
		return parent::createBaseUri();
115
	}
116
117
	public function getWatcher($path = '', $storage = null) {
118
		if (!$storage) {
119
			$storage = $this;
120
		}
121
		if (!isset($this->watcher)) {
122
			$this->watcher = new Watcher($storage);
123
			$this->watcher->setPolicy(\OC\Files\Cache\Watcher::CHECK_ONCE);
124
		}
125
		return $this->watcher;
126
	}
127
128
	public function getRemoteUser() {
129
		return $this->remoteUser;
130
	}
131
132
	public function getRemote() {
133
		return $this->remote;
134
	}
135
136
	public function getMountPoint() {
137
		return $this->mountPoint;
138
	}
139
140
	public function getToken() {
141
		return $this->token;
142
	}
143
144
	public function getPassword() {
145
		return $this->password;
146
	}
147
148
	/**
149
	 * @brief get id of the mount point
150
	 * @return string
151
	 */
152
	public function getId() {
153
		return 'shared::' . md5($this->token . '@' . $this->remote);
154
	}
155
156
	public function getCache($path = '', $storage = null) {
157
		if (is_null($this->cache)) {
158
			$this->cache = new Cache($this, $this->remote, $this->remoteUser);
159
		}
160
		return $this->cache;
161
	}
162
163
	/**
164
	 * check if a file or folder has been updated since $time
165
	 *
166
	 * @param string $path
167
	 * @param int $time
168
	 * @throws \OCP\Files\StorageNotAvailableException
169
	 * @throws \OCP\Files\StorageInvalidException
170
	 * @return bool
171
	 */
172
	public function hasUpdated($path, $time) {
173
		// since for owncloud webdav servers we can rely on etag propagation we only need to check the root of the storage
174
		// because of that we only do one check for the entire storage per request
175
		if ($this->updateChecked) {
176
			return false;
177
		}
178
		$this->updateChecked = true;
179
		try {
180
			return parent::hasUpdated('', $time);
181
		} catch (StorageInvalidException $e) {
182
			// check if it needs to be removed
183
			$this->checkStorageAvailability();
184
			throw $e;
185
		} catch (StorageNotAvailableException $e) {
186
			// check if it needs to be removed or just temp unavailable
187
			$this->checkStorageAvailability();
188
			throw $e;
189
		}
190
	}
191
192
	public function test() {
193
		try {
194
			return parent::test();
195
		} catch (StorageInvalidException $e) {
196
			// check if it needs to be removed
197
			$this->checkStorageAvailability();
198
			throw $e;
199
		} catch (StorageNotAvailableException $e) {
200
			// check if it needs to be removed or just temp unavailable
201
			$this->checkStorageAvailability();
202
			throw $e;
203
		}
204
	}
205
206
	/**
207
	 * Check whether this storage is permanently or temporarily
208
	 * unavailable
209
	 *
210
	 * @throws \OCP\Files\StorageNotAvailableException
211
	 * @throws \OCP\Files\StorageInvalidException
212
	 */
213
	public function checkStorageAvailability() {
214
		// see if we can find out why the share is unavailable
215
		try {
216
			if ( ! $this->propfind('') ) {
217
				// a 404 can either mean that the share no longer exists or there is no ownCloud on the remote
218
				if ($this->testRemote()) {
219
					// valid ownCloud instance means that the public share no longer exists
220
					// since this is permanent (re-sharing the file will create a new token)
221
					// we remove the invalid storage
222
					$this->manager->removeShare($this->mountPoint);
223
					$this->manager->getMountManager()->removeMount($this->mountPoint);
224
					throw new StorageInvalidException();
225
				} else {
226
					// ownCloud instance is gone, likely to be a temporary server configuration error
227
					throw new StorageNotAvailableException();
228
				}
229
			}
230
		} catch (StorageInvalidException $e) {
231
			// auth error, remove share for now (provide a dialog in the future)
232
			$this->manager->removeShare($this->mountPoint);
233
			$this->manager->getMountManager()->removeMount($this->mountPoint);
234
			throw $e;
235
		} catch (\Exception $e) {
236
			throw $e;
237
		}
238
	}
239
240
	public function file_exists($path) {
241
		if ($path === '') {
242
			return true;
243
		} else {
244
			return parent::file_exists($path);
245
		}
246
	}
247
248
	/**
249
	 * check if the configured remote is a valid federated share provider
250
	 *
251
	 * @return bool
252
	 */
253
	protected function testRemote() {
254
		try {
255
			return $this->testRemoteUrl($this->remote . '/ocs-provider/index.php')
256
				|| $this->testRemoteUrl($this->remote . '/ocs-provider/')
257
				|| $this->testRemoteUrl($this->remote . '/status.php');
258
		} catch (\Exception $e) {
259
			return false;
260
		}
261
	}
262
263
	/**
264
	 * @param string $url
265
	 * @return bool
266
	 */
267
	private function testRemoteUrl($url) {
268
		$cache = $this->memcacheFactory->create('files_sharing_remote_url');
269
		if($cache->hasKey($url)) {
270
			return (bool)$cache->get($url);
271
		}
272
273
		$client = $this->httpClient->newClient();
274
		try {
275
			$result = $client->get($url, [
276
				'timeout' => 10,
277
				'connect_timeout' => 10,
278
			])->getBody();
279
			$data = json_decode($result);
280
			$returnValue = (is_object($data) && !empty($data->version));
281
		} catch (ConnectException $e) {
282
			$returnValue = false;
283
		} catch (ClientException $e) {
284
			$returnValue = false;
285
		}
286
287
		$cache->set($url, $returnValue);
288
		return $returnValue;
289
	}
290
291
	public function getOwner($path) {
292
		list(, $remote) = explode('://', $this->remote, 2);
293
		return $this->remoteUser . '@' . $remote;
294
	}
295
296
	public function isSharable($path) {
297
		if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) {
298
			return false;
299
		}
300
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_SHARE);
301
	}
302
	
303
	public function getPermissions($path) {
304
		$response = $this->propfind($path);
305
		if (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) {
306
			$permissions = $response['{http://open-collaboration-services.org/ns}share-permissions'];
307
		} else {
308
			// use default permission if remote server doesn't provide the share permissions
309
			if ($this->is_dir($path)) {
310
				$permissions = \OCP\Constants::PERMISSION_ALL;
311
			} else {
312
				$permissions = \OCP\Constants::PERMISSION_ALL & ~\OCP\Constants::PERMISSION_CREATE;
313
			}
314
		}
315
316
		return $permissions;
317
	}
318
319
}
320