Passed
Push — master ( c1368b...bc6a5e )
by Joas
13:04 queued 17s
created

CheckSetupController::isMemcacheConfigured()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

444
		$this->dispatcher->/** @scrutinizer ignore-call */ 
445
                     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...
445
446
		return $indexInfo->getListOfMissingIndexes();
447
	}
448
449
	protected function hasMissingColumns(): array {
450
		$indexInfo = new MissingColumnInformation();
451
		// Dispatch event so apps can also hint for pending index updates if needed
452
		$event = new GenericEvent($indexInfo);
453
		$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

453
		$this->dispatcher->/** @scrutinizer ignore-call */ 
454
                     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...
454
455
		return $indexInfo->getListOfMissingColumns();
456
	}
457
458
	protected function isSqliteUsed() {
459
		return strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false;
460
	}
461
462
	protected function isReadOnlyConfig(): bool {
463
		return \OC_Helper::isReadOnlyConfigEnabled();
464
	}
465
466
	protected function hasValidTransactionIsolationLevel(): bool {
467
		try {
468
			if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) {
469
				return true;
470
			}
471
472
			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

472
			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

472
			return $this->db->/** @scrutinizer ignore-call */ getTransactionIsolation() === Connection::TRANSACTION_READ_COMMITTED;
Loading history...
473
		} catch (DBALException $e) {
474
			// ignore
475
		}
476
477
		return true;
478
	}
479
480
	protected function hasFileinfoInstalled(): bool {
481
		return \OC_Util::fileInfoLoaded();
482
	}
483
484
	protected function hasWorkingFileLocking(): bool {
485
		return !($this->lockingProvider instanceof NoopLockingProvider);
486
	}
487
488
	protected function getSuggestedOverwriteCliURL(): string {
489
		$suggestedOverwriteCliUrl = '';
490
		if ($this->config->getSystemValue('overwrite.cli.url', '') === '') {
491
			$suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT;
492
			if (!$this->config->getSystemValue('config_is_read_only', false)) {
493
				// Set the overwrite URL when it was not set yet.
494
				$this->config->setSystemValue('overwrite.cli.url', $suggestedOverwriteCliUrl);
495
				$suggestedOverwriteCliUrl = '';
496
			}
497
		}
498
		return $suggestedOverwriteCliUrl;
499
	}
500
501
	protected function getLastCronInfo(): array {
502
		$lastCronRun = $this->config->getAppValue('core', 'lastcron', 0);
503
		return [
504
			'diffInSeconds' => time() - $lastCronRun,
505
			'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

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