Passed
Push — master ( ffa30c...a6a40f )
by Daniel
15:59 queued 14s
created

CheckSetupController::isUsedTlsLibOutdated()   B

Complexity

Conditions 11
Paths 31

Size

Total Lines 47
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 29
nc 31
nop 0
dl 0
loc 47
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bjoern Schiessle <[email protected]>
6
 * @author Christoph Wurst <[email protected]>
7
 * @author Cthulhux <[email protected]>
8
 * @author Daniel Kesselberg <[email protected]>
9
 * @author Derek <[email protected]>
10
 * @author Georg Ehrke <[email protected]>
11
 * @author J0WI <[email protected]>
12
 * @author Joas Schilling <[email protected]>
13
 * @author Julius Härtl <[email protected]>
14
 * @author Ko- <[email protected]>
15
 * @author Lauris Binde <[email protected]>
16
 * @author Lukas Reschke <[email protected]>
17
 * @author Michael Weimann <[email protected]>
18
 * @author Morris Jobke <[email protected]>
19
 * @author nhirokinet <[email protected]>
20
 * @author Robin Appelman <[email protected]>
21
 * @author Robin McCorkell <[email protected]>
22
 * @author Roeland Jago Douma <[email protected]>
23
 * @author Sven Strickroth <[email protected]>
24
 * @author Sylvia van Os <[email protected]>
25
 * @author timm2k <[email protected]>
26
 * @author Timo Förster <[email protected]>
27
 * @author Valdnet <[email protected]>
28
 * @author MichaIng <[email protected]>
29
 *
30
 * @license AGPL-3.0
31
 *
32
 * This code is free software: you can redistribute it and/or modify
33
 * it under the terms of the GNU Affero General Public License, version 3,
34
 * as published by the Free Software Foundation.
35
 *
36
 * This program is distributed in the hope that it will be useful,
37
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
38
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39
 * GNU Affero General Public License for more details.
40
 *
41
 * You should have received a copy of the GNU Affero General Public License, version 3,
42
 * along with this program. If not, see <http://www.gnu.org/licenses/>
43
 *
44
 */
45
namespace OCA\Settings\Controller;
46
47
use bantu\IniGetWrapper\IniGetWrapper;
48
use DirectoryIterator;
49
use Doctrine\DBAL\Exception;
50
use Doctrine\DBAL\Platforms\SqlitePlatform;
51
use Doctrine\DBAL\TransactionIsolationLevel;
52
use GuzzleHttp\Exception\ClientException;
53
use OC;
54
use OC\AppFramework\Http;
55
use OC\DB\Connection;
56
use OC\DB\MissingColumnInformation;
57
use OC\DB\MissingIndexInformation;
58
use OC\DB\MissingPrimaryKeyInformation;
59
use OC\DB\SchemaWrapper;
60
use OC\IntegrityCheck\Checker;
61
use OC\Lock\NoopLockingProvider;
62
use OC\Lock\DBLockingProvider;
63
use OC\MemoryInfo;
64
use OCA\Settings\SetupChecks\CheckUserCertificates;
65
use OCA\Settings\SetupChecks\LdapInvalidUuids;
66
use OCA\Settings\SetupChecks\LegacySSEKeyFormat;
67
use OCA\Settings\SetupChecks\PhpDefaultCharset;
68
use OCA\Settings\SetupChecks\PhpOutputBuffering;
69
use OCA\Settings\SetupChecks\SupportedDatabase;
70
use OCP\App\IAppManager;
71
use OCP\AppFramework\Controller;
72
use OCP\AppFramework\Http\DataDisplayResponse;
73
use OCP\AppFramework\Http\DataResponse;
74
use OCP\AppFramework\Http\RedirectResponse;
75
use OCP\DB\Types;
76
use OCP\Http\Client\IClientService;
77
use OCP\IConfig;
78
use OCP\IDateTimeFormatter;
79
use OCP\IDBConnection;
80
use OCP\IL10N;
81
use OCP\IRequest;
82
use OCP\IServerContainer;
83
use OCP\ITempManager;
84
use OCP\IURLGenerator;
85
use OCP\Lock\ILockingProvider;
86
use OCP\Notification\IManager;
87
use OCP\Security\ISecureRandom;
88
use Psr\Log\LoggerInterface;
89
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
90
use Symfony\Component\EventDispatcher\GenericEvent;
91
92
class CheckSetupController extends Controller {
93
	/** @var IConfig */
94
	private $config;
95
	/** @var IClientService */
96
	private $clientService;
97
	/** @var IURLGenerator */
98
	private $urlGenerator;
99
	/** @var IL10N */
100
	private $l10n;
101
	/** @var Checker */
102
	private $checker;
103
	/** @var LoggerInterface */
104
	private $logger;
105
	/** @var EventDispatcherInterface */
106
	private $dispatcher;
107
	/** @var Connection */
108
	private $db;
109
	/** @var ILockingProvider */
110
	private $lockingProvider;
111
	/** @var IDateTimeFormatter */
112
	private $dateTimeFormatter;
113
	/** @var MemoryInfo */
114
	private $memoryInfo;
115
	/** @var ISecureRandom */
116
	private $secureRandom;
117
	/** @var IniGetWrapper */
118
	private $iniGetWrapper;
119
	/** @var IDBConnection */
120
	private $connection;
121
	/** @var ITempManager */
122
	private $tempManager;
123
	/** @var IManager */
124
	private $manager;
125
	/** @var IAppManager */
126
	private $appManager;
127
	/** @var IServerContainer */
128
	private $serverContainer;
129
130
	public function __construct($AppName,
131
								IRequest $request,
132
								IConfig $config,
133
								IClientService $clientService,
134
								IURLGenerator $urlGenerator,
135
								IL10N $l10n,
136
								Checker $checker,
137
								LoggerInterface $logger,
138
								EventDispatcherInterface $dispatcher,
139
								Connection $db,
140
								ILockingProvider $lockingProvider,
141
								IDateTimeFormatter $dateTimeFormatter,
142
								MemoryInfo $memoryInfo,
143
								ISecureRandom $secureRandom,
144
								IniGetWrapper $iniGetWrapper,
145
								IDBConnection $connection,
146
								ITempManager $tempManager,
147
								IManager $manager,
148
								IAppManager $appManager,
149
								IServerContainer $serverContainer
150
	) {
151
		parent::__construct($AppName, $request);
152
		$this->config = $config;
153
		$this->clientService = $clientService;
154
		$this->urlGenerator = $urlGenerator;
155
		$this->l10n = $l10n;
156
		$this->checker = $checker;
157
		$this->logger = $logger;
158
		$this->dispatcher = $dispatcher;
159
		$this->db = $db;
160
		$this->lockingProvider = $lockingProvider;
161
		$this->dateTimeFormatter = $dateTimeFormatter;
162
		$this->memoryInfo = $memoryInfo;
163
		$this->secureRandom = $secureRandom;
164
		$this->iniGetWrapper = $iniGetWrapper;
165
		$this->connection = $connection;
166
		$this->tempManager = $tempManager;
167
		$this->manager = $manager;
168
		$this->appManager = $appManager;
169
		$this->serverContainer = $serverContainer;
170
	}
171
172
	/**
173
	 * Check if is fair use of free push service
174
	 * @return bool
175
	 */
176
	private function isFairUseOfFreePushService(): bool {
177
		return $this->manager->isFairUseOfFreePushService();
178
	}
179
180
	/**
181
	 * Checks if the server can connect to the internet using HTTPS and HTTP
182
	 * @return bool
183
	 */
184
	private function hasInternetConnectivityProblems(): bool {
185
		if ($this->config->getSystemValue('has_internet_connection', true) === false) {
186
			return false;
187
		}
188
189
		$siteArray = $this->config->getSystemValue('connectivity_check_domains', [
190
			'www.nextcloud.com', 'www.startpage.com', 'www.eff.org', 'www.edri.org'
191
		]);
192
193
		foreach ($siteArray as $site) {
194
			if ($this->isSiteReachable($site)) {
195
				return false;
196
			}
197
		}
198
		return true;
199
	}
200
201
	/**
202
	 * Checks if the Nextcloud server can connect to a specific URL
203
	 * @param string $site site domain or full URL with http/https protocol
204
	 * @return bool
205
	 */
206
	private function isSiteReachable(string $site): bool {
207
		try {
208
			$client = $this->clientService->newClient();
209
			// if there is no protocol, test http:// AND https://
210
			if (preg_match('/^https?:\/\//', $site) !== 1) {
211
				$httpSite = 'http://' . $site . '/';
212
				$client->get($httpSite);
213
				$httpsSite = 'https://' . $site . '/';
214
				$client->get($httpsSite);
215
			} else {
216
				$client->get($site);
217
			}
218
		} catch (\Exception $e) {
219
			$this->logger->error('Cannot connect to: ' . $site, [
220
				'app' => 'internet_connection_check',
221
				'exception' => $e,
222
			]);
223
			return false;
224
		}
225
		return true;
226
	}
227
228
	/**
229
	 * Checks whether a local memcache is installed or not
230
	 * @return bool
231
	 */
232
	private function isMemcacheConfigured() {
233
		return $this->config->getSystemValue('memcache.local', null) !== null;
234
	}
235
236
	/**
237
	 * Whether PHP can generate "secure" pseudorandom integers
238
	 *
239
	 * @return bool
240
	 */
241
	private function isRandomnessSecure() {
242
		try {
243
			$this->secureRandom->generate(1);
244
		} catch (\Exception $ex) {
245
			return false;
246
		}
247
		return true;
248
	}
249
250
	/**
251
	 * Public for the sake of unit-testing
252
	 *
253
	 * @return array
254
	 */
255
	protected function getCurlVersion() {
256
		return curl_version();
257
	}
258
259
	/**
260
	 * Check if the used SSL lib is outdated. Older OpenSSL and NSS versions do
261
	 * have multiple bugs which likely lead to problems in combination with
262
	 * functionality required by ownCloud such as SNI.
263
	 *
264
	 * @link https://github.com/owncloud/core/issues/17446#issuecomment-122877546
265
	 * @link https://bugzilla.redhat.com/show_bug.cgi?id=1241172
266
	 * @return string
267
	 */
268
	private function isUsedTlsLibOutdated() {
269
		// Don't run check when:
270
		// 1. Server has `has_internet_connection` set to false
271
		// 2. AppStore AND S2S is disabled
272
		if (!$this->config->getSystemValue('has_internet_connection', true)) {
273
			return '';
274
		}
275
		if (!$this->config->getSystemValue('appstoreenabled', true)
276
			&& $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'no'
277
			&& $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes') === 'no') {
278
			return '';
279
		}
280
281
		$versionString = $this->getCurlVersion();
282
		if (isset($versionString['ssl_version'])) {
283
			$versionString = $versionString['ssl_version'];
284
		} else {
285
			return '';
286
		}
287
288
		$features = $this->l10n->t('installing and updating apps via the App Store or Federated Cloud Sharing');
289
		if (!$this->config->getSystemValue('appstoreenabled', true)) {
290
			$features = $this->l10n->t('Federated Cloud Sharing');
291
		}
292
293
		// Check if NSS and perform heuristic check
294
		if (strpos($versionString, 'NSS/') === 0) {
295
			try {
296
				$firstClient = $this->clientService->newClient();
297
				$firstClient->get('https://nextcloud.com/');
298
299
				$secondClient = $this->clientService->newClient();
300
				$secondClient->get('https://nextcloud.com/');
301
			} catch (ClientException $e) {
302
				if ($e->getResponse()->getStatusCode() === 400) {
303
					return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['NSS', $versionString, $features]);
304
				}
305
			} catch (\Exception $e) {
306
				$this->logger->warning('error checking curl', [
307
					'app' => 'settings',
308
					'exception' => $e,
309
				]);
310
				return $this->l10n->t('Could not determine if TLS version of cURL is outdated or not because an error happened during the HTTPS request against https://nextcloud.com. Please check the Nextcloud log file for more details.');
311
			}
312
		}
313
314
		return '';
315
	}
316
317
	/**
318
	 * Whether the version is outdated
319
	 *
320
	 * @return bool
321
	 */
322
	protected function isPhpOutdated(): bool {
323
		return PHP_VERSION_ID < 80100;
324
	}
325
326
	/**
327
	 * Whether the php version is still supported (at time of release)
328
	 * according to: https://www.php.net/supported-versions.php
329
	 *
330
	 * @return array
331
	 */
332
	private function isPhpSupported(): array {
333
		return ['eol' => $this->isPhpOutdated(), 'version' => PHP_VERSION];
334
	}
335
336
	/**
337
	 * Check if the reverse proxy configuration is working as expected
338
	 *
339
	 * @return bool
340
	 */
341
	private function forwardedForHeadersWorking(): bool {
342
		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
343
		$remoteAddress = $this->request->getHeader('REMOTE_ADDR');
344
345
		if (empty($trustedProxies) && $this->request->getHeader('X-Forwarded-Host') !== '') {
346
			return false;
347
		}
348
349
		if (\is_array($trustedProxies)) {
350
			if (\in_array($remoteAddress, $trustedProxies, true) && $remoteAddress !== '127.0.0.1') {
351
				return $remoteAddress !== $this->request->getRemoteAddress();
352
			}
353
		} else {
354
			return false;
355
		}
356
357
		// either not enabled or working correctly
358
		return true;
359
	}
360
361
	/**
362
	 * Checks if the correct memcache module for PHP is installed. Only
363
	 * fails if memcached is configured and the working module is not installed.
364
	 *
365
	 * @return bool
366
	 */
367
	private function isCorrectMemcachedPHPModuleInstalled() {
368
		if ($this->config->getSystemValue('memcache.distributed', null) !== '\OC\Memcache\Memcached') {
369
			return true;
370
		}
371
372
		// there are two different memcache modules for PHP
373
		// we only support memcached and not memcache
374
		// https://code.google.com/p/memcached/wiki/PHPClientComparison
375
		return !(!extension_loaded('memcached') && extension_loaded('memcache'));
376
	}
377
378
	/**
379
	 * Checks if set_time_limit is not disabled.
380
	 *
381
	 * @return bool
382
	 */
383
	private function isSettimelimitAvailable() {
384
		if (function_exists('set_time_limit')
385
			&& strpos(ini_get('disable_functions'), 'set_time_limit') === false) {
386
			return true;
387
		}
388
389
		return false;
390
	}
391
392
	/**
393
	 * @return RedirectResponse
394
	 * @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\Overview)
395
	 */
396
	public function rescanFailedIntegrityCheck(): RedirectResponse {
397
		$this->checker->runInstanceVerification();
398
		return new RedirectResponse(
399
			$this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'overview'])
400
		);
401
	}
402
403
	/**
404
	 * @NoCSRFRequired
405
	 * @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\Overview)
406
	 */
407
	public function getFailedIntegrityCheckFiles(): DataDisplayResponse {
408
		if (!$this->checker->isCodeCheckEnforced()) {
409
			return new DataDisplayResponse('Integrity checker has been disabled. Integrity cannot be verified.');
410
		}
411
412
		$completeResults = $this->checker->getResults();
413
414
		if (!empty($completeResults)) {
415
			$formattedTextResponse = 'Technical information
416
=====================
417
The following list covers which files have failed the integrity check. Please read
418
the previous linked documentation to learn more about the errors and how to fix
419
them.
420
421
Results
422
=======
423
';
424
			foreach ($completeResults as $context => $contextResult) {
425
				$formattedTextResponse .= "- $context\n";
426
427
				foreach ($contextResult as $category => $result) {
428
					$formattedTextResponse .= "\t- $category\n";
429
					if ($category !== 'EXCEPTION') {
430
						foreach ($result as $key => $results) {
431
							$formattedTextResponse .= "\t\t- $key\n";
432
						}
433
					} else {
434
						foreach ($result as $key => $results) {
435
							$formattedTextResponse .= "\t\t- $results\n";
436
						}
437
					}
438
				}
439
			}
440
441
			$formattedTextResponse .= '
442
Raw output
443
==========
444
';
445
			$formattedTextResponse .= print_r($completeResults, true);
446
		} else {
447
			$formattedTextResponse = 'No errors have been found.';
448
		}
449
450
451
		return new DataDisplayResponse(
452
			$formattedTextResponse,
453
			Http::STATUS_OK,
454
			[
455
				'Content-Type' => 'text/plain',
456
			]
457
		);
458
	}
459
460
	/**
461
	 * Checks whether a PHP OPcache is properly set up
462
	 * @return string[] The list of OPcache setup recommendations
463
	 */
464
	protected function getOpcacheSetupRecommendations(): array {
465
		// If the module is not loaded, return directly to skip inapplicable checks
466
		if (!extension_loaded('Zend OPcache')) {
467
			return [$this->l10n->t('The PHP OPcache module is not loaded. For better performance it is recommended to load it into your PHP installation.')];
468
		}
469
470
		$recommendations = [];
471
472
		// Check whether Nextcloud is allowed to use the OPcache API
473
		$isPermitted = true;
474
		$permittedPath = $this->iniGetWrapper->getString('opcache.restrict_api');
475
		if (isset($permittedPath) && $permittedPath !== '' && !str_starts_with(\OC::$SERVERROOT, rtrim($permittedPath, '/'))) {
476
			$isPermitted = false;
477
		}
478
479
		if (!$this->iniGetWrapper->getBool('opcache.enable')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->iniGetWrapper->getBool('opcache.enable') of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
480
			$recommendations[] = $this->l10n->t('OPcache is disabled. For better performance, it is recommended to apply <code>opcache.enable=1</code> to your PHP configuration.');
481
482
			// Check for saved comments only when OPcache is currently disabled. If it was enabled, opcache.save_comments=0 would break Nextcloud in the first place.
483
			if (!$this->iniGetWrapper->getBool('opcache.save_comments')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->iniGetWrapper->ge...opcache.save_comments') of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
484
				$recommendations[] = $this->l10n->t('OPcache is configured to remove code comments. With OPcache enabled, <code>opcache.save_comments=1</code> must be set for Nextcloud to function.');
485
			}
486
487
			if (!$isPermitted) {
488
				$recommendations[] = $this->l10n->t('Nextcloud is not allowed to use the OPcache API. With OPcache enabled, it is highly recommended to include all Nextcloud directories with <code>opcache.restrict_api</code> or unset this setting to disable OPcache API restrictions, to prevent errors during Nextcloud core or app upgrades.');
489
			}
490
		} elseif (!$isPermitted) {
491
			$recommendations[] = $this->l10n->t('Nextcloud is not allowed to use the OPcache API. It is highly recommended to include all Nextcloud directories with <code>opcache.restrict_api</code> or unset this setting to disable OPcache API restrictions, to prevent errors during Nextcloud core or app upgrades.');
492
		} elseif ($this->iniGetWrapper->getBool('opcache.file_cache_only')) {
493
			$recommendations[] = $this->l10n->t('The shared memory based OPcache is disabled. For better performance, it is recommended to apply <code>opcache.file_cache_only=0</code> to your PHP configuration and use the file cache as second level cache only.');
494
		} else {
495
			// Check whether opcache_get_status has been explicitly disabled an in case skip usage based checks
496
			$disabledFunctions = $this->iniGetWrapper->getString('disable_functions');
497
			if (isset($disabledFunctions) && str_contains($disabledFunctions, 'opcache_get_status')) {
498
				return [];
499
			}
500
501
			$status = opcache_get_status(false);
502
503
			// Recommend to raise value, if more than 90% of max value is reached
504
			if (
505
				empty($status['opcache_statistics']['max_cached_keys']) ||
506
				($status['opcache_statistics']['num_cached_keys'] / $status['opcache_statistics']['max_cached_keys'] > 0.9)
507
			) {
508
				$recommendations[] = $this->l10n->t('The maximum number of OPcache keys is nearly exceeded. To assure that all scripts can be kept in the cache, it is recommended to apply <code>opcache.max_accelerated_files</code> to your PHP configuration with a value higher than <code>%s</code>.', [($this->iniGetWrapper->getNumeric('opcache.max_accelerated_files') ?: 'currently')]);
509
			}
510
511
			if (
512
				empty($status['memory_usage']['free_memory']) ||
513
				($status['memory_usage']['used_memory'] / $status['memory_usage']['free_memory'] > 9)
514
			) {
515
				$recommendations[] = $this->l10n->t('The OPcache buffer is nearly full. To assure that all scripts can be hold in cache, it is recommended to apply <code>opcache.memory_consumption</code> to your PHP configuration with a value higher than <code>%s</code>.', [($this->iniGetWrapper->getNumeric('opcache.memory_consumption') ?: 'currently')]);
516
			}
517
518
			if (
519
				// Do not recommend to raise the interned strings buffer size above a quarter of the total OPcache size
520
				($this->iniGetWrapper->getNumeric('opcache.interned_strings_buffer') < $this->iniGetWrapper->getNumeric('opcache.memory_consumption') / 4) &&
521
				(
522
					empty($status['interned_strings_usage']['free_memory']) ||
523
					($status['interned_strings_usage']['used_memory'] / $status['interned_strings_usage']['free_memory'] > 9)
524
				)
525
			) {
526
				$recommendations[] = $this->l10n->t('The OPcache interned strings buffer is nearly full. To assure that repeating strings can be effectively cached, it is recommended to apply <code>opcache.interned_strings_buffer</code> to your PHP configuration with a value higher than <code>%s</code>.', [($this->iniGetWrapper->getNumeric('opcache.interned_strings_buffer') ?: 'currently')]);
527
			}
528
		}
529
530
		return $recommendations;
531
	}
532
533
	/**
534
	 * Check if the required FreeType functions are present
535
	 * @return bool
536
	 */
537
	protected function hasFreeTypeSupport() {
538
		return function_exists('imagettfbbox') && function_exists('imagettftext');
539
	}
540
541
	protected function hasMissingIndexes(): array {
542
		$indexInfo = new MissingIndexInformation();
543
		// Dispatch event so apps can also hint for pending index updates if needed
544
		$event = new GenericEvent($indexInfo);
545
		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_INDEXES_EVENT, $event);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\IDBConnection::CHECK_MISSING_INDEXES_EVENT has been deprecated: 22.0.0 this is an internal event ( Ignorable by Annotation )

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

545
		$this->dispatcher->dispatch(/** @scrutinizer ignore-deprecated */ IDBConnection::CHECK_MISSING_INDEXES_EVENT, $event);

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

Loading history...
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with $event. ( Ignorable by Annotation )

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

545
		$this->dispatcher->/** @scrutinizer ignore-call */ 
546
                     dispatch(IDBConnection::CHECK_MISSING_INDEXES_EVENT, $event);

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...
546
547
		return $indexInfo->getListOfMissingIndexes();
548
	}
549
550
	protected function hasMissingPrimaryKeys(): array {
551
		$info = new MissingPrimaryKeyInformation();
552
		// Dispatch event so apps can also hint for pending index updates if needed
553
		$event = new GenericEvent($info);
554
		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_PRIMARY_KEYS_EVENT, $event);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\IDBConnection::CHECK...SING_PRIMARY_KEYS_EVENT has been deprecated: 22.0.0 this is an internal event ( Ignorable by Annotation )

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

554
		$this->dispatcher->dispatch(/** @scrutinizer ignore-deprecated */ IDBConnection::CHECK_MISSING_PRIMARY_KEYS_EVENT, $event);

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

Loading history...
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with $event. ( Ignorable by Annotation )

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

554
		$this->dispatcher->/** @scrutinizer ignore-call */ 
555
                     dispatch(IDBConnection::CHECK_MISSING_PRIMARY_KEYS_EVENT, $event);

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...
555
556
		return $info->getListOfMissingPrimaryKeys();
557
	}
558
559
	protected function hasMissingColumns(): array {
560
		$indexInfo = new MissingColumnInformation();
561
		// Dispatch event so apps can also hint for pending index updates if needed
562
		$event = new GenericEvent($indexInfo);
563
		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_COLUMNS_EVENT, $event);
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with $event. ( Ignorable by Annotation )

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

563
		$this->dispatcher->/** @scrutinizer ignore-call */ 
564
                     dispatch(IDBConnection::CHECK_MISSING_COLUMNS_EVENT, $event);

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...
Deprecated Code introduced by
The constant OCP\IDBConnection::CHECK_MISSING_COLUMNS_EVENT has been deprecated: 22.0.0 this is an internal event ( Ignorable by Annotation )

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

563
		$this->dispatcher->dispatch(/** @scrutinizer ignore-deprecated */ IDBConnection::CHECK_MISSING_COLUMNS_EVENT, $event);

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

Loading history...
564
565
		return $indexInfo->getListOfMissingColumns();
566
	}
567
568
	protected function isSqliteUsed() {
569
		return strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false;
570
	}
571
572
	protected function isReadOnlyConfig(): bool {
573
		return \OC_Helper::isReadOnlyConfigEnabled();
574
	}
575
576
	protected function wasEmailTestSuccessful(): bool {
577
		// Handle the case that the configuration was set before the check was introduced or it was only set via command line and not from the UI
578
		if ($this->config->getAppValue('core', 'emailTestSuccessful', '') === '' && $this->config->getSystemValue('mail_domain', '') === '') {
579
			return false;
580
		}
581
582
		// The mail test was unsuccessful or the config was changed using the UI without verifying with a testmail, hence return false
583
		if ($this->config->getAppValue('core', 'emailTestSuccessful', '') === '0') {
584
			return false;
585
		}
586
587
		return true;
588
	}
589
590
	protected function hasValidTransactionIsolationLevel(): bool {
591
		try {
592
			if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) {
593
				return true;
594
			}
595
596
			return $this->db->getTransactionIsolation() === TransactionIsolationLevel::READ_COMMITTED;
597
		} catch (Exception $e) {
598
			// ignore
599
		}
600
601
		return true;
602
	}
603
604
	protected function hasFileinfoInstalled(): bool {
605
		return \OC_Util::fileInfoLoaded();
606
	}
607
608
	protected function hasWorkingFileLocking(): bool {
609
		return !($this->lockingProvider instanceof NoopLockingProvider);
610
	}
611
612
	protected function hasDBFileLocking(): bool {
613
		return ($this->lockingProvider instanceof DBLockingProvider);
614
	}
615
616
	protected function getSuggestedOverwriteCliURL(): string {
617
		$currentOverwriteCliUrl = $this->config->getSystemValue('overwrite.cli.url', '');
618
		$suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT;
619
620
		// Check correctness by checking if it is a valid URL
621
		if (filter_var($currentOverwriteCliUrl, FILTER_VALIDATE_URL)) {
622
			$suggestedOverwriteCliUrl = '';
623
		}
624
625
		return $suggestedOverwriteCliUrl;
626
	}
627
628
	protected function getLastCronInfo(): array {
629
		$lastCronRun = (int)$this->config->getAppValue('core', 'lastcron', '0');
630
		return [
631
			'diffInSeconds' => time() - $lastCronRun,
632
			'relativeTime' => $this->dateTimeFormatter->formatTimeSpan($lastCronRun),
633
			'backgroundJobsUrl' => $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'server']) . '#backgroundjobs',
634
		];
635
	}
636
637
	protected function getCronErrors() {
638
		$errors = json_decode($this->config->getAppValue('core', 'cronErrors', ''), true);
639
640
		if (is_array($errors)) {
641
			return $errors;
642
		}
643
644
		return [];
645
	}
646
647
	private function isTemporaryDirectoryWritable(): bool {
648
		try {
649
			if (!empty($this->tempManager->getTempBaseDir())) {
650
				return true;
651
			}
652
		} catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
653
		}
654
		return false;
655
	}
656
657
	/**
658
	 * Iterates through the configured app roots and
659
	 * tests if the subdirectories are owned by the same user than the current user.
660
	 *
661
	 * @return array
662
	 */
663
	protected function getAppDirsWithDifferentOwner(): array {
664
		$currentUser = posix_getuid();
665
		$appDirsWithDifferentOwner = [[]];
666
667
		foreach (OC::$APPSROOTS as $appRoot) {
668
			if ($appRoot['writable'] === true) {
669
				$appDirsWithDifferentOwner[] = $this->getAppDirsWithDifferentOwnerForAppRoot($currentUser, $appRoot);
670
			}
671
		}
672
673
		$appDirsWithDifferentOwner = array_merge(...$appDirsWithDifferentOwner);
674
		sort($appDirsWithDifferentOwner);
675
676
		return $appDirsWithDifferentOwner;
677
	}
678
679
	/**
680
	 * Tests if the directories for one apps directory are writable by the current user.
681
	 *
682
	 * @param int $currentUser The current user
683
	 * @param array $appRoot The app root config
684
	 * @return string[] The none writable directory paths inside the app root
685
	 */
686
	private function getAppDirsWithDifferentOwnerForAppRoot(int $currentUser, array $appRoot): array {
687
		$appDirsWithDifferentOwner = [];
688
		$appsPath = $appRoot['path'];
689
		$appsDir = new DirectoryIterator($appRoot['path']);
690
691
		foreach ($appsDir as $fileInfo) {
692
			if ($fileInfo->isDir() && !$fileInfo->isDot()) {
693
				$absAppPath = $appsPath . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
694
				$appDirUser = fileowner($absAppPath);
695
				if ($appDirUser !== $currentUser) {
696
					$appDirsWithDifferentOwner[] = $absAppPath;
697
				}
698
			}
699
		}
700
701
		return $appDirsWithDifferentOwner;
702
	}
703
704
	/**
705
	 * Checks for potential PHP modules that would improve the instance
706
	 *
707
	 * @return string[] A list of PHP modules that is recommended
708
	 */
709
	protected function hasRecommendedPHPModules(): array {
710
		$recommendedPHPModules = [];
711
712
		if (!extension_loaded('intl')) {
713
			$recommendedPHPModules[] = 'intl';
714
		}
715
716
		if (!extension_loaded('sysvsem')) {
717
			// used to limit the usage of resources by preview generator
718
			$recommendedPHPModules[] = 'sysvsem';
719
		}
720
721
		if (!extension_loaded('exif')) {
722
			// used to extract metadata from images
723
			// required for correct orientation of preview images
724
			$recommendedPHPModules[] = 'exif';
725
		}
726
727
		if (!defined('PASSWORD_ARGON2I')) {
728
			// Installing php-sodium on >=php7.4 will provide PASSWORD_ARGON2I
729
			// on previous version argon2 wasn't part of the "standard" extension
730
			// and RedHat disabled it so even installing php-sodium won't provide argon2i
731
			// support in password_hash/password_verify.
732
			$recommendedPHPModules[] = 'sodium';
733
		}
734
735
		return $recommendedPHPModules;
736
	}
737
738
	protected function isImagickEnabled(): bool {
739
		if ($this->config->getAppValue('theming', 'enabled', 'no') === 'yes') {
740
			if (!extension_loaded('imagick')) {
741
				return false;
742
			}
743
		}
744
		return true;
745
	}
746
747
	protected function areWebauthnExtensionsEnabled(): bool {
748
		if (!extension_loaded('bcmath')) {
749
			return false;
750
		}
751
		if (!extension_loaded('gmp')) {
752
			return false;
753
		}
754
		return true;
755
	}
756
757
	protected function is64bit(): bool {
758
		if (PHP_INT_SIZE < 8) {
759
			return false;
760
		} else {
761
			return true;
762
		}
763
	}
764
765
	protected function isMysqlUsedWithoutUTF8MB4(): bool {
766
		return ($this->config->getSystemValue('dbtype', 'sqlite') === 'mysql') && ($this->config->getSystemValue('mysql.utf8mb4', false) === false);
767
	}
768
769
	protected function hasBigIntConversionPendingColumns(): array {
770
		// copy of ConvertFilecacheBigInt::getColumnsByTable()
771
		$tables = [
772
			'activity' => ['activity_id', 'object_id'],
773
			'activity_mq' => ['mail_id'],
774
			'authtoken' => ['id'],
775
			'bruteforce_attempts' => ['id'],
776
			'federated_reshares' => ['share_id'],
777
			'filecache' => ['fileid', 'storage', 'parent', 'mimetype', 'mimepart', 'mtime', 'storage_mtime'],
778
			'filecache_extended' => ['fileid'],
779
			'files_trash' => ['auto_id'],
780
			'file_locks' => ['id'],
781
			'file_metadata' => ['id'],
782
			'jobs' => ['id'],
783
			'mimetypes' => ['id'],
784
			'mounts' => ['id', 'storage_id', 'root_id', 'mount_id'],
785
			'share_external' => ['id', 'parent'],
786
			'storages' => ['numeric_id'],
787
		];
788
789
		$schema = new SchemaWrapper($this->db);
790
		$isSqlite = $this->db->getDatabasePlatform() instanceof SqlitePlatform;
791
		$pendingColumns = [];
792
793
		foreach ($tables as $tableName => $columns) {
794
			if (!$schema->hasTable($tableName)) {
795
				continue;
796
			}
797
798
			$table = $schema->getTable($tableName);
799
			foreach ($columns as $columnName) {
800
				$column = $table->getColumn($columnName);
801
				$isAutoIncrement = $column->getAutoincrement();
802
				$isAutoIncrementOnSqlite = $isSqlite && $isAutoIncrement;
803
				if ($column->getType()->getName() !== Types::BIGINT && !$isAutoIncrementOnSqlite) {
804
					$pendingColumns[] = $tableName . '.' . $columnName;
805
				}
806
			}
807
		}
808
809
		return $pendingColumns;
810
	}
811
812
	protected function isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(): bool {
813
		$objectStore = $this->config->getSystemValue('objectstore', null);
814
		$objectStoreMultibucket = $this->config->getSystemValue('objectstore_multibucket', null);
815
816
		if (!isset($objectStoreMultibucket) && !isset($objectStore)) {
817
			return true;
818
		}
819
820
		if (isset($objectStoreMultibucket['class']) && $objectStoreMultibucket['class'] !== 'OC\\Files\\ObjectStore\\S3') {
821
			return true;
822
		}
823
824
		if (isset($objectStore['class']) && $objectStore['class'] !== 'OC\\Files\\ObjectStore\\S3') {
825
			return true;
826
		}
827
828
		$tempPath = sys_get_temp_dir();
829
		if (!is_dir($tempPath)) {
830
			$this->logger->error('Error while checking the temporary PHP path - it was not properly set to a directory. Returned value: ' . $tempPath);
831
			return false;
832
		}
833
		$freeSpaceInTemp = function_exists('disk_free_space') ? disk_free_space($tempPath) : false;
834
		if ($freeSpaceInTemp === false) {
835
			$this->logger->error('Error while checking the available disk space of temporary PHP path or no free disk space returned. Temporary path: ' . $tempPath);
836
			return false;
837
		}
838
839
		$freeSpaceInTempInGB = $freeSpaceInTemp / 1024 / 1024 / 1024;
840
		if ($freeSpaceInTempInGB > 50) {
841
			return true;
842
		}
843
844
		$this->logger->warning('Checking the available space in the temporary path resulted in ' . round($freeSpaceInTempInGB, 1) . ' GB instead of the recommended 50GB. Path: ' . $tempPath);
845
		return false;
846
	}
847
848
	protected function imageMagickLacksSVGSupport(): bool {
849
		return extension_loaded('imagick') && count(\Imagick::queryFormats('SVG')) === 0;
850
	}
851
852
	/**
853
	 * @return DataResponse
854
	 * @AuthorizedAdminSetting(settings=OCA\Settings\Settings\Admin\Overview)
855
	 */
856
	public function check() {
857
		$phpDefaultCharset = new PhpDefaultCharset();
858
		$phpOutputBuffering = new PhpOutputBuffering();
859
		$legacySSEKeyFormat = new LegacySSEKeyFormat($this->l10n, $this->config, $this->urlGenerator);
860
		$checkUserCertificates = new CheckUserCertificates($this->l10n, $this->config, $this->urlGenerator);
861
		$supportedDatabases = new SupportedDatabase($this->l10n, $this->connection);
862
		$ldapInvalidUuids = new LdapInvalidUuids($this->appManager, $this->l10n, $this->serverContainer);
863
864
		return new DataResponse(
865
			[
866
				'isGetenvServerWorking' => !empty(getenv('PATH')),
867
				'isReadOnlyConfig' => $this->isReadOnlyConfig(),
868
				'hasValidTransactionIsolationLevel' => $this->hasValidTransactionIsolationLevel(),
869
				'wasEmailTestSuccessful' => $this->wasEmailTestSuccessful(),
870
				'hasFileinfoInstalled' => $this->hasFileinfoInstalled(),
871
				'hasWorkingFileLocking' => $this->hasWorkingFileLocking(),
872
				'hasDBFileLocking' => $this->hasDBFileLocking(),
873
				'suggestedOverwriteCliURL' => $this->getSuggestedOverwriteCliURL(),
874
				'cronInfo' => $this->getLastCronInfo(),
875
				'cronErrors' => $this->getCronErrors(),
876
				'isFairUseOfFreePushService' => $this->isFairUseOfFreePushService(),
877
				'serverHasInternetConnectionProblems' => $this->hasInternetConnectivityProblems(),
878
				'isMemcacheConfigured' => $this->isMemcacheConfigured(),
879
				'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'),
880
				'isRandomnessSecure' => $this->isRandomnessSecure(),
881
				'securityDocs' => $this->urlGenerator->linkToDocs('admin-security'),
882
				'isUsedTlsLibOutdated' => $this->isUsedTlsLibOutdated(),
883
				'phpSupported' => $this->isPhpSupported(),
884
				'forwardedForHeadersWorking' => $this->forwardedForHeadersWorking(),
885
				'reverseProxyDocs' => $this->urlGenerator->linkToDocs('admin-reverse-proxy'),
886
				'isCorrectMemcachedPHPModuleInstalled' => $this->isCorrectMemcachedPHPModuleInstalled(),
887
				'hasPassedCodeIntegrityCheck' => $this->checker->hasPassedCheck(),
888
				'codeIntegrityCheckerDocumentation' => $this->urlGenerator->linkToDocs('admin-code-integrity'),
889
				'OpcacheSetupRecommendations' => $this->getOpcacheSetupRecommendations(),
890
				'isSettimelimitAvailable' => $this->isSettimelimitAvailable(),
891
				'hasFreeTypeSupport' => $this->hasFreeTypeSupport(),
892
				'missingPrimaryKeys' => $this->hasMissingPrimaryKeys(),
893
				'missingIndexes' => $this->hasMissingIndexes(),
894
				'missingColumns' => $this->hasMissingColumns(),
895
				'isSqliteUsed' => $this->isSqliteUsed(),
896
				'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'),
897
				'isMemoryLimitSufficient' => $this->memoryInfo->isMemoryLimitSufficient(),
898
				'appDirsWithDifferentOwner' => $this->getAppDirsWithDifferentOwner(),
899
				'isImagickEnabled' => $this->isImagickEnabled(),
900
				'areWebauthnExtensionsEnabled' => $this->areWebauthnExtensionsEnabled(),
901
				'is64bit' => $this->is64bit(),
902
				'recommendedPHPModules' => $this->hasRecommendedPHPModules(),
903
				'pendingBigIntConversionColumns' => $this->hasBigIntConversionPendingColumns(),
904
				'isMysqlUsedWithoutUTF8MB4' => $this->isMysqlUsedWithoutUTF8MB4(),
905
				'isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed' => $this->isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(),
906
				'reverseProxyGeneratedURL' => $this->urlGenerator->getAbsoluteURL('index.php'),
907
				'imageMagickLacksSVGSupport' => $this->imageMagickLacksSVGSupport(),
908
				PhpDefaultCharset::class => ['pass' => $phpDefaultCharset->run(), 'description' => $phpDefaultCharset->description(), 'severity' => $phpDefaultCharset->severity()],
909
				PhpOutputBuffering::class => ['pass' => $phpOutputBuffering->run(), 'description' => $phpOutputBuffering->description(), 'severity' => $phpOutputBuffering->severity()],
910
				LegacySSEKeyFormat::class => ['pass' => $legacySSEKeyFormat->run(), 'description' => $legacySSEKeyFormat->description(), 'severity' => $legacySSEKeyFormat->severity(), 'linkToDocumentation' => $legacySSEKeyFormat->linkToDocumentation()],
911
				CheckUserCertificates::class => ['pass' => $checkUserCertificates->run(), 'description' => $checkUserCertificates->description(), 'severity' => $checkUserCertificates->severity(), 'elements' => $checkUserCertificates->elements()],
912
				'isDefaultPhoneRegionSet' => $this->config->getSystemValueString('default_phone_region', '') !== '',
913
				SupportedDatabase::class => ['pass' => $supportedDatabases->run(), 'description' => $supportedDatabases->description(), 'severity' => $supportedDatabases->severity()],
914
				'temporaryDirectoryWritable' => $this->isTemporaryDirectoryWritable(),
915
				LdapInvalidUuids::class => ['pass' => $ldapInvalidUuids->run(), 'description' => $ldapInvalidUuids->description(), 'severity' => $ldapInvalidUuids->severity()],
916
			]
917
		);
918
	}
919
}
920