Issues (752)

plugins/filesbackendOwncloud/php/class.backend.php (11 issues)

1
<?php
2
3
namespace Files\Backend\Owncloud;
4
5
require_once __DIR__ . "/../../files/php/Files/Backend/Webdav/sabredav/FilesWebDavClient.php";
6
require_once __DIR__ . "/../../files/php/Files/Backend/class.abstract_backend.php";
7
require_once __DIR__ . "/../../files/php/Files/Backend/class.exception.php";
8
require_once __DIR__ . "/../../files/php/Files/Backend/interface.quota.php";
9
require_once __DIR__ . "/../../files/php/Files/Backend/interface.version.php";
10
require_once __DIR__ . "/../../files/php/Files/Backend/interface.sharing.php";
11
require_once __DIR__ . "/lib/ocsapi/class.ocsclient.php";
12
13
use Files\Backend\AbstractBackend;
14
use Files\Backend\Exception as BackendException;
15
use Files\Backend\iFeatureSharing;
16
use Files\Backend\Webdav\sabredav\FilesWebDavClient;
0 ignored issues
show
The type Files\Backend\Webdav\sabredav\FilesWebDavClient was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use OCSAPI\Exception\ConnectionException;
18
use OCSAPI\Exception\FileNotFoundException;
19
use OCSAPI\ocsclient;
20
use OCSAPI\ocsshare;
21
use Sabre\DAV\Client;
22
use Sabre\DAV\Exception;
23
use Sabre\HTTP\ClientException;
24
25
/**
26
 * This is a file backend for owncloud servers.
27
 * It requires the Webdav File Backend!
28
 *
29
 * @class   Backend
30
 *
31
 * @extends AbstractBackend
32
 */
33
class Backend extends \Files\Backend\Webdav\Backend implements iFeatureSharing {
34
	/**
35
	 * @var ocsclient the OCS Api client
36
	 */
37
	public $ocs_client;
38
39
	/**
40
	 * @constructor
41
	 */
42
	public function __construct() {
43
		// initialization
44
		$this->debug = PLUGIN_FILESBROWSER_LOGLEVEL === "DEBUG" ? true : false;
0 ignored issues
show
The condition Files\Backend\Owncloud\P...ER_LOGLEVEL === 'DEBUG' is always false.
Loading history...
45
46
		$this->init_form();
47
48
		// set backend description
49
		$this->backendDescription = _("With this backend, you can connect to any ownCloud server.");
50
51
		// set backend display name
52
		$this->backendDisplayName = "ownCloud";
53
54
		// set backend version
55
		// TODO: this should be changed on every release
56
		$this->backendVersion = "3.0";
57
58
		// Backend name used in translations
59
		$this->backendTransName = _('Files ownCloud Backend: ');
60
	}
61
62
	/**
63
	 * Initialise form fields.
64
	 */
65
	private function init_form() {
66
		$this->formConfig = [
67
			"labelAlign" => "left",
68
			"columnCount" => 1,
69
			"labelWidth" => 80,
70
			"defaults" => [
71
				"width" => 292,
72
			],
73
		];
74
75
		$this->formFields = [
76
			[
77
				"name" => "server_address",
78
				"fieldLabel" => _('Server address'),
79
				"editor" => [
80
					"allowBlank" => false,
81
				],
82
			],
83
			[
84
				"name" => "server_port",
85
				"fieldLabel" => _('Server port'),
86
				"editor" => [
87
					"ref" => "../../portField",
88
					"allowBlank" => false,
89
				],
90
			],
91
			[
92
				"name" => "server_ssl",
93
				"fieldLabel" => _('Use TLS'),
94
				"editor" => [
95
					"xtype" => "checkbox",
96
					"listeners" => [
97
						"check" => "Zarafa.plugins.files.data.Actions.onCheckSSL", // this javascript function will be called!
98
					],
99
				],
100
			],
101
			[
102
				"name" => "server_path",
103
				"fieldLabel" => _('Webdav base path'),
104
				"editor" => [
105
					"allowBlank" => false,
106
				],
107
			],
108
			[
109
				"name" => "user",
110
				"fieldLabel" => _('Username'),
111
				"editor" => [
112
					"ref" => "../../usernameField",
113
				],
114
			],
115
			[
116
				"name" => "password",
117
				"fieldLabel" => _('Password'),
118
				"editor" => [
119
					"ref" => "../../passwordField",
120
					"inputType" => "password",
121
				],
122
			],
123
			[
124
				"name" => "use_grommunio_credentials",
125
				"fieldLabel" => _('Use grommunio credentials'),
126
				"editor" => [
127
					"xtype" => "checkbox",
128
					"listeners" => [
129
						"check" => "Zarafa.plugins.files.data.Actions.onCheckCredentials", // this javascript function will be called!
130
					],
131
				],
132
			],
133
		];
134
135
		$this->metaConfig = [
136
			"success" => true,
137
			"metaData" => [
138
				"fields" => $this->formFields,
139
				"formConfig" => $this->formConfig,
140
			],
141
			"data" => [
142
				"server_address" => $_SERVER['HTTP_HOST'],
143
				"server_ssl" => true,
144
				"server_port" => "443",
145
				"server_path" => "/files/remote.php/webdav",
146
				"use_grommunio_credentials" => true,
147
			],
148
		];
149
	}
150
151
	/**
152
	 * Opens the connection to the webdav server.
153
	 *
154
	 * @return bool true if action succeeded
155
	 *
156
	 * @throws BackendException if connection is not successful
157
	 */
158
	#[\Override]
159
	public function open() {
160
		// check if curl is available
161
		$serverHasCurl = function_exists('curl_version');
162
		if (!$serverHasCurl) {
163
			$e = new BackendException($this->parseErrorCodeToMessage(self::WD_ERR_NO_CURL), 500);
164
			$e->setTitle($this->backendTransName . _('php-curl is not available'));
165
166
			throw $e;
167
		}
168
169
		$davsettings = [
170
			'baseUri' => $this->webdavUrl(),
171
			'userName' => $this->user,
172
			'password' => $this->pass,
173
			'authType' => Client::AUTH_BASIC,
174
		];
175
176
		try {
177
			$this->sabre_client = new FilesWebDavClient($davsettings);
178
			$this->sabre_client->addCurlSetting(CURLOPT_SSL_VERIFYPEER, !$this->allowselfsigned);
179
180
			$this->ocs_client = new ocsclient($this->getOwncloudBaseURL(), $this->user, $this->pass, $this->allowselfsigned);
181
182
			return true;
183
		}
184
		catch (Exception $e) {
185
			$this->log('Failed to open: ' . $e->getMessage());
186
			if (intval($e->getHTTPCode()) == 401) {
187
				$e = new BackendException($this->parseErrorCodeToMessage(self::WD_ERR_UNAUTHORIZED), $e->getHTTPCode());
188
				$e->setTitle($this->backendTransName . _('Access denied'));
189
190
				throw $e;
191
			}
192
			$e = new BackendException($this->parseErrorCodeToMessage(self::WD_ERR_UNREACHABLE), $e->getHTTPCode());
193
			$e->setTitle($this->backendTransName . _('Connection failed'));
194
195
			throw $e;
196
		}
197
	}
198
199
	/**
200
	 * /**
201
	 * Copy a collection on webdav server
202
	 * Duplicates a collection on the webdav server (serverside).
203
	 * All work is done on the webdav server. If you set param overwrite as true,
204
	 * the target will be overwritten.
205
	 *
206
	 * @param string $src_path  Source path
207
	 * @param string $dst_path  Destination path
208
	 * @param bool   $overwrite Overwrite if collection exists in $dst_path
209
	 * @param bool   $coll      set this to true if you want to copy a folder
210
	 *
211
	 * @return bool true if action succeeded
212
	 *
213
	 * @throws BackendException if request is not successful
214
	 */
215
	private function copy($src_path, $dst_path, $overwrite, $coll) {
0 ignored issues
show
The method copy() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
216
		$time_start = microtime(true);
217
		$src_path = $this->removeSlash($src_path);
218
		$dst_path = $this->webdavUrl() . $this->removeSlash($dst_path);
219
		$this->log("[COPY] start for dir: {$src_path} -> {$dst_path}");
220
		if ($overwrite) {
221
			$overwrite = 'T';
222
		}
223
		else {
224
			$overwrite = 'F';
225
		}
226
227
		$settings = ["Destination" => $dst_path, 'Overwrite' => $overwrite];
228
		if ($coll) {
229
			$settings = ["Destination" => $dst_path, 'Depth' => 'Infinity'];
230
		}
231
232
		try {
233
			$response = $this->sabre_client->request("COPY", $src_path, null, $settings);
234
			$time_end = microtime(true);
235
			$time = $time_end - $time_start;
236
			$this->log("[COPY] done in {$time} seconds: " . $response['statusCode']);
237
238
			return true;
239
		}
240
		catch (ClientException $e) {
241
			$e = new BackendException($this->parseErrorCodeToMessage($e->getCode()), $e->getCode());
242
			$e->setTitle($this->backendTransName . _('Sabre error'));
243
244
			throw $e;
245
		}
246
		catch (Exception $e) {
247
			$this->log('[COPY] fatal: ' . $e->getMessage());
248
			$e = new BackendException($this->parseErrorCodeToMessage($e->getHTTPCode()), $e->getHTTPCode());
249
			$e->setTitle($this->backendTransName . _('Copying failed'));
250
251
			throw $e;
252
		}
253
	}
254
255
	/**
256
	 * This function will return a user friendly error string.
257
	 *
258
	 * @param number $error_code A error code
259
	 *
260
	 * @return string userfriendly error message
261
	 */
262
	private function parseErrorCodeToMessage($error_code) {
263
		$error = intval($error_code);
264
265
		$msg = _('Unknown error');
266
		$contactAdmin = _('Please contact your system administrator.');
267
268
		return match ($error) {
269
			CURLE_BAD_PASSWORD_ENTERED, self::WD_ERR_UNAUTHORIZED => _('Unauthorized. Wrong username or password.'),
270
			CURLE_SSL_CONNECT_ERROR, CURLE_COULDNT_RESOLVE_HOST, CURLE_COULDNT_CONNECT, CURLE_OPERATION_TIMEOUTED, self::WD_ERR_UNREACHABLE => _('File server is not reachable. Please verify the file server URL.'),
271
			self::WD_ERR_FORBIDDEN => _('You don\'t have enough permissions to view this file or folder.'),
272
			self::WD_ERR_NOTFOUND => _('The file or folder is not available anymore.'),
273
			self::WD_ERR_TIMEOUT => _('Connection to the file server timed out. Please check again later.'),
274
			self::WD_ERR_LOCKED => _('This file is locked by another user. Please try again later.'),
275
			self::WD_ERR_FAILED_DEPENDENCY => _('The request failed.') . ' ' . $contactAdmin,
276
			// This is a general error, might be thrown due to a wrong IP, but we don't know.
277
			self::WD_ERR_INTERNAL => _('The file server encountered an internal problem.') . ' ' . $contactAdmin,
278
			self::WD_ERR_TMP => _('We could not write to temporary directory.') . ' ' . $contactAdmin,
279
			self::WD_ERR_FEATURES => _('We could not retrieve list of server features.') . ' ' . $contactAdmin,
280
			self::WD_ERR_NO_CURL => _('PHP-Curl is not available.') . ' ' . $contactAdmin,
281
			default => $msg,
282
		};
283
	}
284
285
	/**
286
	 * a simple php error_log wrapper.
287
	 *
288
	 * @param string $err_string error message
289
	 */
290
	private function log($err_string) {
291
		if ($this->debug) {
292
			error_log("[BACKEND_OWNCLOUD]: " . $err_string);
293
		}
294
	}
295
296
	/**
297
	 * Get the base URL of Owncloud.
298
	 * For example: http://demo.owncloud.com/owncloud.
299
	 *
300
	 * @return string
301
	 */
302
	private function getOwncloudBaseURL() {
303
		$webdavurl = $this->webdavUrl();
304
305
		return substr($webdavurl, 0, strlen($webdavurl) - strlen("/remote.php/webdav/"));
306
	}
307
308
	/**
309
	 * ============================ FEATURE FUNCTIONS ========================.
310
	 */
311
312
	/**
313
	 * Return the version string of the server backend.
314
	 *
315
	 * @return string
316
	 */
317
	#[\Override]
318
	public function getServerVersion() {
319
		// check if curl is available
320
		$serverHasCurl = function_exists('curl_version');
321
		if (!$serverHasCurl) {
322
			throw new BackendException($this->parseErrorCodeToMessage(self::WD_ERR_NO_CURL), 500);
323
		}
324
325
		$url = $this->getOwncloudBaseURL() . "/status.php";
326
327
		// try to get the contents of the owncloud status page
328
		$ch = curl_init();
329
		curl_setopt($ch, CURLOPT_AUTOREFERER, true);
330
		curl_setopt($ch, CURLOPT_TIMEOUT, 3); // timeout of 3 seconds
331
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
332
		curl_setopt($ch, CURLOPT_URL, $url);
333
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
334
		if ($this->allowselfsigned) {
335
			curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
336
			curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
337
		}
338
		$versiondata = curl_exec($ch);
339
		$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
340
		curl_close($ch);
341
342
		if ($httpcode && $httpcode == "200" && $versiondata) {
343
			$versions = json_decode($versiondata);
0 ignored issues
show
It seems like $versiondata can also be of type true; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

343
			$versions = json_decode(/** @scrutinizer ignore-type */ $versiondata);
Loading history...
344
			$version = $versions->versionstring;
345
		}
346
		else {
347
			$version = "Undetected (no ownCloud?)";
348
		}
349
350
		return $version;
351
	}
352
353
	/**
354
	 * Get all shares in the specified folder.
355
	 *
356
	 * The response array will look like:
357
	 *
358
	 * array(
359
	 *  path1 => array(
360
	 *      id1 => details1,
361
	 *      id2 => details2
362
	 *  ),
363
	 *  path2 => array(
364
	 *      id1 => ....
365
	 *  )
366
	 * )
367
	 *
368
	 * @return array
369
	 */
370
	public function getShares($path) {
371
		$result = [];
372
373
		$this->log('[GETSHARES]: loading shares for folder: ' . $path);
374
375
		try {
376
			$this->ocs_client->loadShares($path);
377
		}
378
		catch (ConnectionException $e) {
379
			$this->log('[GETSHARES]: connection exception while loading shares: ' . $e->getMessage() . " " . $e->getCode());
380
		}
381
		$shares = $this->ocs_client->getAllShares();
382
383
		$result[$path] = [];
384
		if ($shares !== false) {
0 ignored issues
show
The condition $shares !== false is always true.
Loading history...
385
			foreach ($shares as $id => $options) {
386
				$result[$path][$id] = [
387
					"shared" => true,
388
					"id" => $options->getId(),
389
					"path" => $options->getPath(),
390
					"shareType" => $options->getShareType(),
391
					"permissions" => $options->getPermissions(),
392
					"expiration" => $options->getExpiration(),
393
					"token" => $options->getToken(),
394
					"url" => $options->getUrl(),
395
					"shareWith" => $options->getShareWith(),
396
					"shareWithDisplayname" => $options->getShareWithDisplayname(),
397
				];
398
			}
399
		}
400
401
		return $result;
402
	}
403
404
	/**
405
	 * Get details about the shared files/folders.
406
	 *
407
	 * The response array will look like:
408
	 *
409
	 * array(
410
	 *  path1 => array(
411
	 *      id1 => details1,
412
	 *      id2 => details2
413
	 *  ),
414
	 *  path2 => array(
415
	 *      id1 => ....
416
	 *  )
417
	 * )
418
	 *
419
	 * @param $patharray Simple array with path's to files or folders
0 ignored issues
show
The type Files\Backend\Owncloud\Simple was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
420
	 *
421
	 * @return array
422
	 */
423
	public function sharingDetails($patharray) {
424
		$result = [];
425
426
		// performance optimization
427
		// fetch all shares - so we only need one request
428
		if (count($patharray) > 1) {
429
			try {
430
				$this->ocs_client->loadShares();
431
			}
432
			catch (ConnectionException $e) {
433
				$this->log('[SHARINGDETAILS]: connection exception while loading shares: ' . $e->getMessage() . " " . $e->getCode());
434
			}
435
436
			/** @var ocsshare[] $shares */
437
			$shares = $this->ocs_client->getAllShares();
438
			foreach ($patharray as $path) {
439
				$result[$path] = [];
440
				foreach ($shares as $id => $details) {
441
					if ($details->getPath() == $path) {
442
						$result[$path][$id] = [
443
							"shared" => true,
444
							"id" => $details->getId(),
445
							"shareType" => $details->getShareType(),
446
							"permissions" => $details->getPermissions(),
447
							"expiration" => $details->getExpiration(),
448
							"token" => $details->getToken(),
449
							"url" => $details->getUrl(),
450
							"shareWith" => $details->getShareWith(),
451
							"shareWithDisplayname" => $details->getShareWithDisplayname(),
452
						];
453
					}
454
				}
455
			}
456
		}
457
		else {
458
			if (count($patharray) == 1) {
459
				try {
460
					$shares = $this->ocs_client->loadShareByPath($patharray[0]);
461
				}
462
				catch (FileNotFoundException) {
463
					$shares = false;
464
				}
465
466
				$result[$patharray[0]] = [];
467
468
				if ($shares !== false) {
469
					foreach ($shares as $id => $share) {
470
						$result[$patharray[0]][$id] = [
471
							"shared" => true,
472
							"id" => $share->getId(),
473
							"shareType" => $share->getShareType(),
474
							"permissions" => $share->getPermissions(),
475
							"expiration" => $share->getExpiration(),
476
							"token" => $share->getToken(),
477
							"url" => $share->getUrl(),
478
							"shareWith" => $share->getShareWith(),
479
							"shareWithDisplayname" => $share->getShareWithDisplayName(),
480
						];
481
					}
482
				}
483
			}
484
			else {
485
				return false; // $patharray was empty...
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
486
			}
487
		}
488
489
		return $result;
490
	}
491
492
	/**
493
	 * Share one or multiple files.
494
	 * As the sharing dialog might differ for different backends, it is implemented as
495
	 * MetaForm - meaning that the argumentnames/count might differ.
496
	 * That's the cause why this function uses an array as parameter.
497
	 *
498
	 * $shareparams should look somehow like this:
499
	 *
500
	 * array(
501
	 *      "path1" => options1,
502
	 *      "path2" => options2
503
	 *
504
	 *      or
505
	 *
506
	 *      "id1" => options1 (ONLY if $update = true)
507
	 * )
508
	 *
509
	 * @param bool $update
510
	 *
511
	 * @return bool
512
	 */
513
	public function share($shareparams, $update = false) {
514
		$result = [];
515
		if (count($shareparams) > 0) {
516
			/** @var string $path */
517
			foreach ($shareparams as $path => $options) {
518
				$path = rtrim($path, "/");
519
				$this->log('path: ' . $path);
520
				if (!$update) {
521
					$share = $this->ocs_client->createShare($path, $options);
522
					$result[$path] = [
523
						"shared" => true,
524
						"id" => $share->getId(),
525
						"token" => $share->getToken(),
526
						"url" => $share->getUrl(),
527
					];
528
				}
529
				else {
530
					foreach ($options as $key => $value) {
531
						$this->ocs_client->updateShare($path, $key, $value);
532
					}
533
					$result[$path] = [
534
						"shared" => true,
535
						"id" => $path,
536
					];
537
				}
538
			}
539
		}
540
		else {
541
			$this->log('No share params given');
542
543
			return false; // no shareparams...
544
		}
545
546
		return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result returns the type array which is incompatible with the documented return type boolean.
Loading history...
547
	}
548
549
	/**
550
	 * Disable sharing for the given files/folders.
551
	 *
552
	 * @return bool
553
	 *
554
	 * @throws ConnectionException
555
	 */
556
	public function unshare($idarray) {
557
		foreach ($idarray as $id) {
558
			$this->ocs_client->deleteShare($id);
559
		}
560
561
		return true;
562
	}
563
564
	/*
565
	 * Get Recipients that could be shared with, matching the search string
566
	 *
567
	 * @param $search Searchstring to use
0 ignored issues
show
The type Files\Backend\Owncloud\Searchstring was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
568
	 * @return The response from the osc client API
0 ignored issues
show
The type Files\Backend\Owncloud\The was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
569
	 */
570
	public function getRecipients($search) {
571
		return $this->ocs_client->getRecipients($search);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->ocs_client->getRecipients($search) returns the type array|array<mixed,array<integer,string>>|false which is incompatible with the documented return type Files\Backend\Owncloud\The.
Loading history...
572
	}
573
}
574