Passed
Push — master ( cdfad9...e39d65 )
by Joas
12:09 queued 10s
created

CheckSetupController::hasMissingPrimaryKeys()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

451
		$this->dispatcher->/** @scrutinizer ignore-call */ 
452
                     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...
452
453
		return $indexInfo->getListOfMissingIndexes();
454
	}
455
456
	protected function hasMissingPrimaryKeys(): array {
457
		$info = new MissingPrimaryKeyInformation();
458
		// Dispatch event so apps can also hint for pending index updates if needed
459
		$event = new GenericEvent($info);
460
		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_PRIMARY_KEYS_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

460
		$this->dispatcher->/** @scrutinizer ignore-call */ 
461
                     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...
461
462
		return $info->getListOfMissingPrimaryKeys();
463
	}
464
465
	protected function hasMissingColumns(): array {
466
		$indexInfo = new MissingColumnInformation();
467
		// Dispatch event so apps can also hint for pending index updates if needed
468
		$event = new GenericEvent($indexInfo);
469
		$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

469
		$this->dispatcher->/** @scrutinizer ignore-call */ 
470
                     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...
470
471
		return $indexInfo->getListOfMissingColumns();
472
	}
473
474
	protected function isSqliteUsed() {
475
		return strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false;
476
	}
477
478
	protected function isReadOnlyConfig(): bool {
479
		return \OC_Helper::isReadOnlyConfigEnabled();
480
	}
481
482
	protected function hasValidTransactionIsolationLevel(): bool {
483
		try {
484
			if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) {
485
				return true;
486
			}
487
488
			return $this->db->getTransactionIsolation() === Connection::TRANSACTION_READ_COMMITTED;
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Connection...NSACTION_READ_COMMITTED has been deprecated: Use TransactionIsolationLevel::READ_COMMITTED. ( Ignorable by Annotation )

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

488
			return $this->db->getTransactionIsolation() === /** @scrutinizer ignore-deprecated */ Connection::TRANSACTION_READ_COMMITTED;

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...
Bug introduced by
The method getTransactionIsolation() does not exist on OCP\IDBConnection. Since it exists in all sub-types, consider adding an abstract or default implementation to OCP\IDBConnection. ( Ignorable by Annotation )

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

488
			return $this->db->/** @scrutinizer ignore-call */ getTransactionIsolation() === Connection::TRANSACTION_READ_COMMITTED;
Loading history...
489
		} catch (DBALException $e) {
490
			// ignore
491
		}
492
493
		return true;
494
	}
495
496
	protected function hasFileinfoInstalled(): bool {
497
		return \OC_Util::fileInfoLoaded();
498
	}
499
500
	protected function hasWorkingFileLocking(): bool {
501
		return !($this->lockingProvider instanceof NoopLockingProvider);
502
	}
503
504
	protected function getSuggestedOverwriteCliURL(): string {
505
		$suggestedOverwriteCliUrl = '';
506
		if ($this->config->getSystemValue('overwrite.cli.url', '') === '') {
507
			$suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT;
508
			if (!$this->config->getSystemValue('config_is_read_only', false)) {
509
				// Set the overwrite URL when it was not set yet.
510
				$this->config->setSystemValue('overwrite.cli.url', $suggestedOverwriteCliUrl);
511
				$suggestedOverwriteCliUrl = '';
512
			}
513
		}
514
		return $suggestedOverwriteCliUrl;
515
	}
516
517
	protected function getLastCronInfo(): array {
518
		$lastCronRun = $this->config->getAppValue('core', 'lastcron', 0);
519
		return [
520
			'diffInSeconds' => time() - $lastCronRun,
521
			'relativeTime' => $this->dateTimeFormatter->formatTimeSpan($lastCronRun),
0 ignored issues
show
Bug introduced by
$lastCronRun of type string is incompatible with the type DateTime|integer expected by parameter $timestamp of OCP\IDateTimeFormatter::formatTimeSpan(). ( Ignorable by Annotation )

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

521
			'relativeTime' => $this->dateTimeFormatter->formatTimeSpan(/** @scrutinizer ignore-type */ $lastCronRun),
Loading history...
522
			'backgroundJobsUrl' => $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'server']) . '#backgroundjobs',
523
		];
524
	}
525
526
	protected function getCronErrors() {
527
		$errors = json_decode($this->config->getAppValue('core', 'cronErrors', ''), true);
528
529
		if (is_array($errors)) {
530
			return $errors;
531
		}
532
533
		return [];
534
	}
535
536
	protected function hasOpcacheLoaded(): bool {
537
		return extension_loaded('Zend OPcache');
538
	}
539
540
	/**
541
	 * Iterates through the configured app roots and
542
	 * tests if the subdirectories are owned by the same user than the current user.
543
	 *
544
	 * @return array
545
	 */
546
	protected function getAppDirsWithDifferentOwner(): array {
547
		$currentUser = posix_getuid();
548
		$appDirsWithDifferentOwner = [[]];
549
550
		foreach (OC::$APPSROOTS as $appRoot) {
551
			if ($appRoot['writable'] === true) {
552
				$appDirsWithDifferentOwner[] = $this->getAppDirsWithDifferentOwnerForAppRoot($currentUser, $appRoot);
553
			}
554
		}
555
556
		$appDirsWithDifferentOwner = array_merge(...$appDirsWithDifferentOwner);
557
		sort($appDirsWithDifferentOwner);
558
559
		return $appDirsWithDifferentOwner;
560
	}
561
562
	/**
563
	 * Tests if the directories for one apps directory are writable by the current user.
564
	 *
565
	 * @param int $currentUser The current user
566
	 * @param array $appRoot The app root config
567
	 * @return string[] The none writable directory paths inside the app root
568
	 */
569
	private function getAppDirsWithDifferentOwnerForAppRoot(int $currentUser, array $appRoot): array {
570
		$appDirsWithDifferentOwner = [];
571
		$appsPath = $appRoot['path'];
572
		$appsDir = new DirectoryIterator($appRoot['path']);
573
574
		foreach ($appsDir as $fileInfo) {
575
			if ($fileInfo->isDir() && !$fileInfo->isDot()) {
576
				$absAppPath = $appsPath . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
577
				$appDirUser = fileowner($absAppPath);
578
				if ($appDirUser !== $currentUser) {
579
					$appDirsWithDifferentOwner[] = $absAppPath;
580
				}
581
			}
582
		}
583
584
		return $appDirsWithDifferentOwner;
585
	}
586
587
	/**
588
	 * Checks for potential PHP modules that would improve the instance
589
	 *
590
	 * @return string[] A list of PHP modules that is recommended
591
	 */
592
	protected function hasRecommendedPHPModules(): array {
593
		$recommendedPHPModules = [];
594
595
		if (!extension_loaded('intl')) {
596
			$recommendedPHPModules[] = 'intl';
597
		}
598
599
		if (!extension_loaded('bcmath')) {
600
			$recommendedPHPModules[] = 'bcmath';
601
		}
602
603
		if (!extension_loaded('gmp')) {
604
			$recommendedPHPModules[] = 'gmp';
605
		}
606
607
		if ($this->config->getAppValue('theming', 'enabled', 'no') === 'yes') {
608
			if (!extension_loaded('imagick')) {
609
				$recommendedPHPModules[] = 'imagick';
610
			}
611
		}
612
613
		return $recommendedPHPModules;
614
	}
615
616
	protected function isMysqlUsedWithoutUTF8MB4(): bool {
617
		return ($this->config->getSystemValue('dbtype', 'sqlite') === 'mysql') && ($this->config->getSystemValue('mysql.utf8mb4', false) === false);
618
	}
619
620
	protected function hasBigIntConversionPendingColumns(): array {
621
		// copy of ConvertFilecacheBigInt::getColumnsByTable()
622
		$tables = [
623
			'activity' => ['activity_id', 'object_id'],
624
			'activity_mq' => ['mail_id'],
625
			'authtoken' => ['id'],
626
			'bruteforce_attempts' => ['id'],
627
			'filecache' => ['fileid', 'storage', 'parent', 'mimetype', 'mimepart', 'mtime', 'storage_mtime'],
628
			'filecache_extended' => ['fileid'],
629
			'file_locks' => ['id'],
630
			'jobs' => ['id'],
631
			'mimetypes' => ['id'],
632
			'mounts' => ['id', 'storage_id', 'root_id', 'mount_id'],
633
			'storages' => ['numeric_id'],
634
		];
635
636
		$schema = new SchemaWrapper($this->db);
637
		$isSqlite = $this->db->getDatabasePlatform() instanceof SqlitePlatform;
638
		$pendingColumns = [];
639
640
		foreach ($tables as $tableName => $columns) {
641
			if (!$schema->hasTable($tableName)) {
642
				continue;
643
			}
644
645
			$table = $schema->getTable($tableName);
646
			foreach ($columns as $columnName) {
647
				$column = $table->getColumn($columnName);
648
				$isAutoIncrement = $column->getAutoincrement();
649
				$isAutoIncrementOnSqlite = $isSqlite && $isAutoIncrement;
650
				if ($column->getType()->getName() !== Types::BIGINT && !$isAutoIncrementOnSqlite) {
651
					$pendingColumns[] = $tableName . '.' . $columnName;
652
				}
653
			}
654
		}
655
656
		return $pendingColumns;
657
	}
658
659
	protected function isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(): bool {
660
		$objectStore = $this->config->getSystemValue('objectstore', null);
661
		$objectStoreMultibucket = $this->config->getSystemValue('objectstore_multibucket', null);
662
663
		if (!isset($objectStoreMultibucket) && !isset($objectStore)) {
664
			return true;
665
		}
666
667
		if (isset($objectStoreMultibucket['class']) && $objectStoreMultibucket['class'] !== 'OC\\Files\\ObjectStore\\S3') {
668
			return true;
669
		}
670
671
		if (isset($objectStore['class']) && $objectStore['class'] !== 'OC\\Files\\ObjectStore\\S3') {
672
			return true;
673
		}
674
675
		$tempPath = sys_get_temp_dir();
676
		if (!is_dir($tempPath)) {
677
			$this->logger->error('Error while checking the temporary PHP path - it was not properly set to a directory. value: ' . $tempPath);
678
			return false;
679
		}
680
		$freeSpaceInTemp = disk_free_space($tempPath);
681
		if ($freeSpaceInTemp === false) {
682
			$this->logger->error('Error while checking the available disk space of temporary PHP path - no free disk space returned. temporary path: ' . $tempPath);
683
			return false;
684
		}
685
686
		$freeSpaceInTempInGB = $freeSpaceInTemp / 1024 / 1024 / 1024;
687
		if ($freeSpaceInTempInGB > 50) {
688
			return true;
689
		}
690
691
		$this->logger->warning('Checking the available space in the temporary path resulted in ' . round($freeSpaceInTempInGB, 1) . ' GB instead of the recommended 50GB. Path: ' . $tempPath);
692
		return false;
693
	}
694
695
	protected function imageMagickLacksSVGSupport(): bool {
696
		return extension_loaded('imagick') && count(\Imagick::queryFormats('SVG')) === 0;
697
	}
698
699
	/**
700
	 * @return DataResponse
701
	 */
702
	public function check() {
703
		$phpDefaultCharset = new PhpDefaultCharset();
704
		$phpOutputBuffering = new PhpOutputBuffering();
705
		$legacySSEKeyFormat = new LegacySSEKeyFormat($this->l10n, $this->config, $this->urlGenerator);
706
		$checkUserCertificates = new CheckUserCertificates($this->l10n, $this->config, $this->urlGenerator);
707
708
		return new DataResponse(
709
			[
710
				'isGetenvServerWorking' => !empty(getenv('PATH')),
711
				'isReadOnlyConfig' => $this->isReadOnlyConfig(),
712
				'hasValidTransactionIsolationLevel' => $this->hasValidTransactionIsolationLevel(),
713
				'hasFileinfoInstalled' => $this->hasFileinfoInstalled(),
714
				'hasWorkingFileLocking' => $this->hasWorkingFileLocking(),
715
				'suggestedOverwriteCliURL' => $this->getSuggestedOverwriteCliURL(),
716
				'cronInfo' => $this->getLastCronInfo(),
717
				'cronErrors' => $this->getCronErrors(),
718
				'serverHasInternetConnectionProblems' => $this->hasInternetConnectivityProblems(),
719
				'isMemcacheConfigured' => $this->isMemcacheConfigured(),
720
				'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'),
721
				'isRandomnessSecure' => $this->isRandomnessSecure(),
722
				'securityDocs' => $this->urlGenerator->linkToDocs('admin-security'),
723
				'isUsedTlsLibOutdated' => $this->isUsedTlsLibOutdated(),
724
				'phpSupported' => $this->isPhpSupported(),
725
				'forwardedForHeadersWorking' => $this->forwardedForHeadersWorking(),
726
				'reverseProxyDocs' => $this->urlGenerator->linkToDocs('admin-reverse-proxy'),
727
				'isCorrectMemcachedPHPModuleInstalled' => $this->isCorrectMemcachedPHPModuleInstalled(),
728
				'hasPassedCodeIntegrityCheck' => $this->checker->hasPassedCheck(),
729
				'codeIntegrityCheckerDocumentation' => $this->urlGenerator->linkToDocs('admin-code-integrity'),
730
				'isOpcacheProperlySetup' => $this->isOpcacheProperlySetup(),
731
				'hasOpcacheLoaded' => $this->hasOpcacheLoaded(),
732
				'phpOpcacheDocumentation' => $this->urlGenerator->linkToDocs('admin-php-opcache'),
733
				'isSettimelimitAvailable' => $this->isSettimelimitAvailable(),
734
				'hasFreeTypeSupport' => $this->hasFreeTypeSupport(),
735
				'missingPrimaryKeys' => $this->hasMissingPrimaryKeys(),
736
				'missingIndexes' => $this->hasMissingIndexes(),
737
				'missingColumns' => $this->hasMissingColumns(),
738
				'isSqliteUsed' => $this->isSqliteUsed(),
739
				'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'),
740
				'isMemoryLimitSufficient' => $this->memoryInfo->isMemoryLimitSufficient(),
741
				'appDirsWithDifferentOwner' => $this->getAppDirsWithDifferentOwner(),
742
				'recommendedPHPModules' => $this->hasRecommendedPHPModules(),
743
				'pendingBigIntConversionColumns' => $this->hasBigIntConversionPendingColumns(),
744
				'isMysqlUsedWithoutUTF8MB4' => $this->isMysqlUsedWithoutUTF8MB4(),
745
				'isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed' => $this->isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(),
746
				'reverseProxyGeneratedURL' => $this->urlGenerator->getAbsoluteURL('index.php'),
747
				'imageMagickLacksSVGSupport' => $this->imageMagickLacksSVGSupport(),
748
				PhpDefaultCharset::class => ['pass' => $phpDefaultCharset->run(), 'description' => $phpDefaultCharset->description(), 'severity' => $phpDefaultCharset->severity()],
749
				PhpOutputBuffering::class => ['pass' => $phpOutputBuffering->run(), 'description' => $phpOutputBuffering->description(), 'severity' => $phpOutputBuffering->severity()],
750
				LegacySSEKeyFormat::class => ['pass' => $legacySSEKeyFormat->run(), 'description' => $legacySSEKeyFormat->description(), 'severity' => $legacySSEKeyFormat->severity(), 'linkToDocumentation' => $legacySSEKeyFormat->linkToDocumentation()],
751
				CheckUserCertificates::class => ['pass' => $checkUserCertificates->run(), 'description' => $checkUserCertificates->description(), 'severity' => $checkUserCertificates->severity(), 'elements' => $checkUserCertificates->elements()],
752
			]
753
		);
754
	}
755
}
756