Completed
Push — master ( fb17f1...ff55bc )
by Blizzz
17:03
created

Setup::getSystemInfo()   B

Complexity

Conditions 8
Paths 32

Size

Total Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 32
nop 1
dl 0
loc 63
rs 7.5628
c 0
b 0
f 0

How to fix   Long Method   

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 Administrator "Administrator@WINDOWS-2012"
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Bart Visscher <[email protected]>
8
 * @author Bernhard Posselt <[email protected]>
9
 * @author Brice Maron <[email protected]>
10
 * @author Christoph Wurst <[email protected]>
11
 * @author Frank Isemann <[email protected]>
12
 * @author François Kubler <[email protected]>
13
 * @author Jakob Sack <[email protected]>
14
 * @author Joas Schilling <[email protected]>
15
 * @author KB7777 <[email protected]>
16
 * @author Lukas Reschke <[email protected]>
17
 * @author Morris Jobke <[email protected]>
18
 * @author Robert Scheck <[email protected]>
19
 * @author Robin Appelman <[email protected]>
20
 * @author Roeland Jago Douma <[email protected]>
21
 * @author Sean Comeau <[email protected]>
22
 * @author Serge Martin <[email protected]>
23
 * @author Thomas Müller <[email protected]>
24
 * @author Vincent Petry <[email protected]>
25
 *
26
 * @license AGPL-3.0
27
 *
28
 * This code is free software: you can redistribute it and/or modify
29
 * it under the terms of the GNU Affero General Public License, version 3,
30
 * as published by the Free Software Foundation.
31
 *
32
 * This program is distributed in the hope that it will be useful,
33
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35
 * GNU Affero General Public License for more details.
36
 *
37
 * You should have received a copy of the GNU Affero General Public License, version 3,
38
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
39
 *
40
 */
41
42
namespace OC;
43
44
use bantu\IniGetWrapper\IniGetWrapper;
45
use Exception;
46
use InvalidArgumentException;
47
use OC\App\AppStore\Bundles\BundleFetcher;
48
use OC\Authentication\Token\DefaultTokenCleanupJob;
49
use OC\Authentication\Token\DefaultTokenProvider;
50
use OC\Log\Rotate;
51
use OC\Preview\BackgroundCleanupJob;
52
use OCP\Defaults;
53
use OCP\IL10N;
54
use OCP\ILogger;
55
use OCP\Security\ISecureRandom;
56
57
class Setup {
58
	/** @var SystemConfig */
59
	protected $config;
60
	/** @var IniGetWrapper */
61
	protected $iniWrapper;
62
	/** @var IL10N */
63
	protected $l10n;
64
	/** @var Defaults */
65
	protected $defaults;
66
	/** @var ILogger */
67
	protected $logger;
68
	/** @var ISecureRandom */
69
	protected $random;
70
	/** @var Installer */
71
	protected $installer;
72
73
	/**
74
	 * @param SystemConfig $config
75
	 * @param IniGetWrapper $iniWrapper
76
	 * @param IL10N $l10n
77
	 * @param Defaults $defaults
78
	 * @param ILogger $logger
79
	 * @param ISecureRandom $random
80
	 * @param Installer $installer
81
	 */
82
	public function __construct(SystemConfig $config,
83
						 IniGetWrapper $iniWrapper,
84
						 IL10N $l10n,
85
						 Defaults $defaults,
86
						 ILogger $logger,
87
						 ISecureRandom $random,
88
						 Installer $installer
89
		) {
90
		$this->config = $config;
91
		$this->iniWrapper = $iniWrapper;
92
		$this->l10n = $l10n;
93
		$this->defaults = $defaults;
94
		$this->logger = $logger;
95
		$this->random = $random;
96
		$this->installer = $installer;
97
	}
98
99
	static protected $dbSetupClasses = [
100
		'mysql' => \OC\Setup\MySQL::class,
101
		'pgsql' => \OC\Setup\PostgreSQL::class,
102
		'oci'   => \OC\Setup\OCI::class,
103
		'sqlite' => \OC\Setup\Sqlite::class,
104
		'sqlite3' => \OC\Setup\Sqlite::class,
105
	];
106
107
	/**
108
	 * Wrapper around the "class_exists" PHP function to be able to mock it
109
	 * @param string $name
110
	 * @return bool
111
	 */
112
	protected function class_exists($name) {
113
		return class_exists($name);
114
	}
115
116
	/**
117
	 * Wrapper around the "is_callable" PHP function to be able to mock it
118
	 * @param string $name
119
	 * @return bool
120
	 */
121
	protected function is_callable($name) {
122
		return is_callable($name);
123
	}
124
125
	/**
126
	 * Wrapper around \PDO::getAvailableDrivers
127
	 *
128
	 * @return array
129
	 */
130
	protected function getAvailableDbDriversForPdo() {
131
		return \PDO::getAvailableDrivers();
132
	}
133
134
	/**
135
	 * Get the available and supported databases of this instance
136
	 *
137
	 * @param bool $allowAllDatabases
138
	 * @return array
139
	 * @throws Exception
140
	 */
141
	public function getSupportedDatabases($allowAllDatabases = false) {
142
		$availableDatabases = [
143
			'sqlite' =>  [
144
				'type' => 'pdo',
145
				'call' => 'sqlite',
146
				'name' => 'SQLite',
147
			],
148
			'mysql' => [
149
				'type' => 'pdo',
150
				'call' => 'mysql',
151
				'name' => 'MySQL/MariaDB',
152
			],
153
			'pgsql' => [
154
				'type' => 'pdo',
155
				'call' => 'pgsql',
156
				'name' => 'PostgreSQL',
157
			],
158
			'oci' => [
159
				'type' => 'function',
160
				'call' => 'oci_connect',
161
				'name' => 'Oracle',
162
			],
163
		];
164
		if ($allowAllDatabases) {
165
			$configuredDatabases = array_keys($availableDatabases);
166
		} else {
167
			$configuredDatabases = $this->config->getValue('supportedDatabases',
168
				['sqlite', 'mysql', 'pgsql']);
169
		}
170
		if(!is_array($configuredDatabases)) {
171
			throw new Exception('Supported databases are not properly configured.');
172
		}
173
174
		$supportedDatabases = array();
175
176
		foreach($configuredDatabases as $database) {
177
			if(array_key_exists($database, $availableDatabases)) {
178
				$working = false;
179
				$type = $availableDatabases[$database]['type'];
180
				$call = $availableDatabases[$database]['call'];
181
182
				if ($type === 'function') {
183
					$working = $this->is_callable($call);
184
				} elseif($type === 'pdo') {
185
					$working = in_array($call, $this->getAvailableDbDriversForPdo(), true);
186
				}
187
				if($working) {
188
					$supportedDatabases[$database] = $availableDatabases[$database]['name'];
189
				}
190
			}
191
		}
192
193
		return $supportedDatabases;
194
	}
195
196
	/**
197
	 * Gathers system information like database type and does
198
	 * a few system checks.
199
	 *
200
	 * @return array of system info, including an "errors" value
201
	 * in case of errors/warnings
202
	 */
203
	public function getSystemInfo($allowAllDatabases = false) {
204
		$databases = $this->getSupportedDatabases($allowAllDatabases);
205
206
		$dataDir = $this->config->getValue('datadirectory', \OC::$SERVERROOT.'/data');
207
208
		$errors = [];
209
210
		// Create data directory to test whether the .htaccess works
211
		// Notice that this is not necessarily the same data directory as the one
212
		// that will effectively be used.
213
		if(!file_exists($dataDir)) {
214
			@mkdir($dataDir);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
215
		}
216
		$htAccessWorking = true;
217
		if (is_dir($dataDir) && is_writable($dataDir)) {
218
			// Protect data directory here, so we can test if the protection is working
219
			self::protectDataDirectory();
220
221
			try {
222
				$util = new \OC_Util();
223
				$htAccessWorking = $util->isHtaccessWorking(\OC::$server->getConfig());
224
			} catch (\OC\HintException $e) {
225
				$errors[] = [
226
					'error' => $e->getMessage(),
227
					'hint' => $e->getHint(),
228
				];
229
				$htAccessWorking = false;
230
			}
231
		}
232
233
		if (\OC_Util::runningOnMac()) {
234
			$errors[] = [
235
				'error' => $this->l10n->t(
236
					'Mac OS X is not supported and %s will not work properly on this platform. ' .
237
					'Use it at your own risk! ',
238
					[$this->defaults->getName()]
239
				),
240
				'hint' => $this->l10n->t('For the best results, please consider using a GNU/Linux server instead.'),
241
			];
242
		}
243
244
		if($this->iniWrapper->getString('open_basedir') !== '' && PHP_INT_SIZE === 4) {
245
			$errors[] = [
246
				'error' => $this->l10n->t(
247
					'It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. ' .
248
					'This will lead to problems with files over 4 GB and is highly discouraged.',
249
					[$this->defaults->getName()]
250
				),
251
				'hint' => $this->l10n->t('Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP.'),
252
			];
253
		}
254
255
		return array(
256
			'hasSQLite' => isset($databases['sqlite']),
257
			'hasMySQL' => isset($databases['mysql']),
258
			'hasPostgreSQL' => isset($databases['pgsql']),
259
			'hasOracle' => isset($databases['oci']),
260
			'databases' => $databases,
261
			'directory' => $dataDir,
262
			'htaccessWorking' => $htAccessWorking,
263
			'errors' => $errors,
264
		);
265
	}
266
267
	/**
268
	 * @param $options
269
	 * @return array
270
	 */
271
	public function install($options) {
272
		$l = $this->l10n;
273
274
		$error = array();
275
		$dbType = $options['dbtype'];
276
277
		if(empty($options['adminlogin'])) {
278
			$error[] = $l->t('Set an admin username.');
279
		}
280
		if(empty($options['adminpass'])) {
281
			$error[] = $l->t('Set an admin password.');
282
		}
283
		if(empty($options['directory'])) {
284
			$options['directory'] = \OC::$SERVERROOT."/data";
285
		}
286
287
		if (!isset(self::$dbSetupClasses[$dbType])) {
288
			$dbType = 'sqlite';
289
		}
290
291
		$username = htmlspecialchars_decode($options['adminlogin']);
292
		$password = htmlspecialchars_decode($options['adminpass']);
293
		$dataDir = htmlspecialchars_decode($options['directory']);
294
295
		$class = self::$dbSetupClasses[$dbType];
296
		/** @var \OC\Setup\AbstractDatabase $dbSetup */
297
		$dbSetup = new $class($l, $this->config, $this->logger, $this->random);
298
		$error = array_merge($error, $dbSetup->validate($options));
299
300
		// validate the data directory
301
		if ((!is_dir($dataDir) && !mkdir($dataDir)) || !is_writable($dataDir)) {
302
			$error[] = $l->t("Can't create or write into the data directory %s", array($dataDir));
303
		}
304
305
		if (!empty($error)) {
306
			return $error;
307
		}
308
309
		$request = \OC::$server->getRequest();
310
311
		//no errors, good
312
		if(isset($options['trusted_domains'])
313
		    && is_array($options['trusted_domains'])) {
314
			$trustedDomains = $options['trusted_domains'];
315
		} else {
316
			$trustedDomains = [$request->getInsecureServerHost()];
317
		}
318
319
		//use sqlite3 when available, otherwise sqlite2 will be used.
320
		if ($dbType === 'sqlite' && class_exists('SQLite3')) {
321
			$dbType = 'sqlite3';
322
		}
323
324
		//generate a random salt that is used to salt the local user passwords
325
		$salt = $this->random->generate(30);
326
		// generate a secret
327
		$secret = $this->random->generate(48);
328
329
		//write the config file
330
		$newConfigValues = [
331
			'passwordsalt'		=> $salt,
332
			'secret'			=> $secret,
333
			'trusted_domains'	=> $trustedDomains,
334
			'datadirectory'		=> $dataDir,
335
			'dbtype'			=> $dbType,
336
			'version'			=> implode('.', \OCP\Util::getVersion()),
337
		];
338
339
		if ($this->config->getValue('overwrite.cli.url', null) === null) {
340
			$newConfigValues['overwrite.cli.url'] = $request->getServerProtocol() . '://' . $request->getInsecureServerHost() . \OC::$WEBROOT;
341
		}
342
343
		$this->config->setValues($newConfigValues);
344
345
		try {
346
			$dbSetup->initialize($options);
347
			$dbSetup->setupDatabase($username);
348
			// apply necessary migrations
349
			$dbSetup->runMigrations();
350
		} catch (\OC\DatabaseSetupException $e) {
351
			$error[] = [
352
				'error' => $e->getMessage(),
353
				'hint' => $e->getHint(),
354
			];
355
			return $error;
356
		} catch (Exception $e) {
357
			$error[] = [
358
				'error' => 'Error while trying to create admin user: ' . $e->getMessage(),
359
				'hint' => '',
360
			];
361
			return $error;
362
		}
363
364
		//create the user and group
365
		$user =  null;
366
		try {
367
			$user = \OC::$server->getUserManager()->createUser($username, $password);
368
			if (!$user) {
369
				$error[] = "User <$username> could not be created.";
370
			}
371
		} catch(Exception $exception) {
372
			$error[] = $exception->getMessage();
373
		}
374
375
		if (empty($error)) {
376
			$config = \OC::$server->getConfig();
377
			$config->setAppValue('core', 'installedat', microtime(true));
378
			$config->setAppValue('core', 'lastupdatedat', microtime(true));
379
			$config->setAppValue('core', 'vendor', $this->getVendor());
380
381
			$group =\OC::$server->getGroupManager()->createGroup('admin');
382
			$group->addUser($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by \OC::$server->getUserMan...r($username, $password) on line 367 can also be of type false or null; however, OC\Group\Group::addUser() does only seem to accept object<OCP\IUser>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
383
384
			// Install shipped apps and specified app bundles
385
			Installer::installShippedApps();
386
			$bundleFetcher = new BundleFetcher(\OC::$server->getL10N('lib'));
387
			$defaultInstallationBundles = $bundleFetcher->getDefaultInstallationBundle();
388
			foreach($defaultInstallationBundles as $bundle) {
389
				try {
390
					$this->installer->installAppBundle($bundle);
391
				} catch (Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
392
			}
393
394
			// create empty file in data dir, so we can later find
395
			// out that this is indeed an ownCloud data directory
396
			file_put_contents($config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').'/.ocdata', '');
397
398
			// Update .htaccess files
399
			self::updateHtaccess();
400
			self::protectDataDirectory();
401
402
			self::installBackgroundJobs();
403
404
			//and we are done
405
			$config->setSystemValue('installed', true);
406
407
			// Create a session token for the newly created user
408
			// The token provider requires a working db, so it's not injected on setup
409
			/* @var $userSession User\Session */
410
			$userSession = \OC::$server->getUserSession();
411
			$defaultTokenProvider = \OC::$server->query(DefaultTokenProvider::class);
412
			$userSession->setTokenProvider($defaultTokenProvider);
413
			$userSession->login($username, $password);
414
			$userSession->createSessionToken($request, $userSession->getUser()->getUID(), $username, $password);
415
		}
416
417
		return $error;
418
	}
419
420
	public static function installBackgroundJobs() {
421
		$jobList = \OC::$server->getJobList();
422
		$jobList->add(DefaultTokenCleanupJob::class);
423
		$jobList->add(Rotate::class);
424
		$jobList->add(BackgroundCleanupJob::class);
425
	}
426
427
	/**
428
	 * @return string Absolute path to htaccess
429
	 */
430
	private function pathToHtaccess() {
431
		return \OC::$SERVERROOT.'/.htaccess';
432
	}
433
434
	/**
435
	 * Find webroot from config
436
	 *
437
	 * @param SystemConfig $config
438
	 * @return string
439
	 * @throws InvalidArgumentException when invalid value for overwrite.cli.url
440
	 */
441
	private static function findWebRoot(SystemConfig $config): string {
442
		// For CLI read the value from overwrite.cli.url
443
		if (\OC::$CLI) {
444
			$webRoot = $config->getValue('overwrite.cli.url', '');
445
			if ($webRoot === '') {
446
				throw new InvalidArgumentException('overwrite.cli.url is empty');
447
			}
448
			$webRoot = parse_url($webRoot, PHP_URL_PATH);
449
			if ($webRoot === null) {
450
				throw new InvalidArgumentException('invalid value for overwrite.cli.url');
451
			}
452
			$webRoot = rtrim($webRoot, '/');
453
		} else {
454
			$webRoot = !empty(\OC::$WEBROOT) ? \OC::$WEBROOT : '/';
455
		}
456
457
		return $webRoot;
458
	}
459
460
	/**
461
	 * Append the correct ErrorDocument path for Apache hosts
462
	 *
463
	 * @return bool True when success, False otherwise
464
	 * @throws \OCP\AppFramework\QueryException
465
	 */
466
	public static function updateHtaccess() {
467
		$config = \OC::$server->getSystemConfig();
468
469
		try {
470
			$webRoot = self::findWebRoot($config);
471
		} catch (InvalidArgumentException $e) {
472
			return false;
473
		}
474
475
		$setupHelper = new \OC\Setup(
476
			$config,
477
			\OC::$server->getIniWrapper(),
478
			\OC::$server->getL10N('lib'),
479
			\OC::$server->query(Defaults::class),
480
			\OC::$server->getLogger(),
481
			\OC::$server->getSecureRandom(),
482
			\OC::$server->query(Installer::class)
483
		);
484
485
		$htaccessContent = file_get_contents($setupHelper->pathToHtaccess());
486
		$content = "#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####\n";
487
		$htaccessContent = explode($content, $htaccessContent, 2)[0];
488
489
		//custom 403 error page
490
		$content .= "\nErrorDocument 403 " . $webRoot . '/';
491
492
		//custom 404 error page
493
		$content .= "\nErrorDocument 404 " . $webRoot . '/';
494
495
		// Add rewrite rules if the RewriteBase is configured
496
		$rewriteBase = $config->getValue('htaccess.RewriteBase', '');
497
		if($rewriteBase !== '') {
498
			$content .= "\n<IfModule mod_rewrite.c>";
499
			$content .= "\n  Options -MultiViews";
500
			$content .= "\n  RewriteRule ^core/js/oc.js$ index.php [PT,E=PATH_INFO:$1]";
501
			$content .= "\n  RewriteRule ^core/preview.png$ index.php [PT,E=PATH_INFO:$1]";
502
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !\\.(css|js|svg|gif|png|html|ttf|woff|ico|jpg|jpeg)$";
503
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !core/img/favicon.ico$";
504
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !core/img/manifest.json$";
505
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/remote.php";
506
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/public.php";
507
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/cron.php";
508
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/core/ajax/update.php";
509
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/status.php";
510
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/ocs/v1.php";
511
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/ocs/v2.php";
512
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/robots.txt";
513
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/updater/";
514
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/ocs-provider/";
515
			$content .= "\n  RewriteCond %{REQUEST_URI} !^/\\.well-known/(acme-challenge|pki-validation)/.*";
516
			$content .= "\n  RewriteRule . index.php [PT,E=PATH_INFO:$1]";
517
			$content .= "\n  RewriteBase " . $rewriteBase;
518
			$content .= "\n  <IfModule mod_env.c>";
519
			$content .= "\n    SetEnv front_controller_active true";
520
			$content .= "\n    <IfModule mod_dir.c>";
521
			$content .= "\n      DirectorySlash off";
522
			$content .= "\n    </IfModule>";
523
			$content .= "\n  </IfModule>";
524
			$content .= "\n</IfModule>";
525
		}
526
527
		if ($content !== '') {
528
			//suppress errors in case we don't have permissions for it
529
			return (bool) @file_put_contents($setupHelper->pathToHtaccess(), $htaccessContent.$content . "\n");
530
		}
531
532
		return false;
533
	}
534
535
	public static function protectDataDirectory() {
536
		//Require all denied
537
		$now =  date('Y-m-d H:i:s');
538
		$content = "# Generated by Nextcloud on $now\n";
539
		$content.= "# line below if for Apache 2.4\n";
540
		$content.= "<ifModule mod_authz_core.c>\n";
541
		$content.= "Require all denied\n";
542
		$content.= "</ifModule>\n\n";
543
		$content.= "# line below if for Apache 2.2\n";
544
		$content.= "<ifModule !mod_authz_core.c>\n";
545
		$content.= "deny from all\n";
546
		$content.= "Satisfy All\n";
547
		$content.= "</ifModule>\n\n";
548
		$content.= "# section for Apache 2.2 and 2.4\n";
549
		$content.= "<ifModule mod_autoindex.c>\n";
550
		$content.= "IndexIgnore *\n";
551
		$content.= "</ifModule>\n";
552
553
		$baseDir = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data');
554
		file_put_contents($baseDir . '/.htaccess', $content);
555
		file_put_contents($baseDir . '/index.html', '');
556
	}
557
558
	/**
559
	 * Return vendor from which this version was published
560
	 *
561
	 * @return string Get the vendor
562
	 *
563
	 * Copy of \OC\Updater::getVendor()
564
	 */
565
	private function getVendor() {
566
		// this should really be a JSON file
567
		require \OC::$SERVERROOT . '/version.php';
568
		/** @var string $vendor */
569
		return (string) $vendor;
0 ignored issues
show
Bug introduced by
The variable $vendor does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
570
	}
571
}
572