Completed
Push — master ( 497cd7...5651fd )
by Morris
24:04 queued 08:33
created

Setup   D

Complexity

Total Complexity 50

Size/Duplication

Total Lines 483
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 19

Importance

Changes 0
Metric Value
wmc 50
lcom 1
cbo 19
dl 0
loc 483
rs 4.4426
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A class_exists() 0 3 1
A is_callable() 0 3 1
A getAvailableDbDriversForPdo() 0 3 1
B getSupportedDatabases() 0 54 8
C getSystemInfo() 0 63 8
A __construct() 0 14 1
F install() 0 150 20
A installBackgroundJobs() 0 5 1
A pathToHtaccess() 0 3 1
B updateHtaccess() 0 68 6
A protectDataDirectory() 0 22 1
A getVendor() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like Setup often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Setup, and based on these observations, apply Extract Interface, too.

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 OC\App\AppStore\Bundles\BundleFetcher;
47
use OC\Authentication\Token\DefaultTokenCleanupJob;
48
use OC\Authentication\Token\DefaultTokenProvider;
49
use OC\Log\Rotate;
50
use OCP\Defaults;
51
use OCP\IL10N;
52
use OCP\ILogger;
53
use OCP\Security\ISecureRandom;
54
55
class Setup {
56
	/** @var SystemConfig */
57
	protected $config;
58
	/** @var IniGetWrapper */
59
	protected $iniWrapper;
60
	/** @var IL10N */
61
	protected $l10n;
62
	/** @var Defaults */
63
	protected $defaults;
64
	/** @var ILogger */
65
	protected $logger;
66
	/** @var ISecureRandom */
67
	protected $random;
68
69
	/**
70
	 * @param SystemConfig $config
71
	 * @param IniGetWrapper $iniWrapper
72
	 * @param IL10N $l10n
73
	 * @param Defaults $defaults
74
	 * @param ILogger $logger
75
	 * @param ISecureRandom $random
76
	 */
77
	public function __construct(SystemConfig $config,
78
						 IniGetWrapper $iniWrapper,
79
						 IL10N $l10n,
80
						 Defaults $defaults,
81
						 ILogger $logger,
82
						 ISecureRandom $random
83
		) {
84
		$this->config = $config;
85
		$this->iniWrapper = $iniWrapper;
86
		$this->l10n = $l10n;
87
		$this->defaults = $defaults;
88
		$this->logger = $logger;
89
		$this->random = $random;
90
	}
91
92
	static protected $dbSetupClasses = [
93
		'mysql' => \OC\Setup\MySQL::class,
94
		'pgsql' => \OC\Setup\PostgreSQL::class,
95
		'oci'   => \OC\Setup\OCI::class,
96
		'sqlite' => \OC\Setup\Sqlite::class,
97
		'sqlite3' => \OC\Setup\Sqlite::class,
98
	];
99
100
	/**
101
	 * Wrapper around the "class_exists" PHP function to be able to mock it
102
	 * @param string $name
103
	 * @return bool
104
	 */
105
	protected function class_exists($name) {
106
		return class_exists($name);
107
	}
108
109
	/**
110
	 * Wrapper around the "is_callable" PHP function to be able to mock it
111
	 * @param string $name
112
	 * @return bool
113
	 */
114
	protected function is_callable($name) {
115
		return is_callable($name);
116
	}
117
118
	/**
119
	 * Wrapper around \PDO::getAvailableDrivers
120
	 *
121
	 * @return array
122
	 */
123
	protected function getAvailableDbDriversForPdo() {
124
		return \PDO::getAvailableDrivers();
125
	}
126
127
	/**
128
	 * Get the available and supported databases of this instance
129
	 *
130
	 * @param bool $allowAllDatabases
131
	 * @return array
132
	 * @throws Exception
133
	 */
134
	public function getSupportedDatabases($allowAllDatabases = false) {
135
		$availableDatabases = [
136
			'sqlite' =>  [
137
				'type' => 'pdo',
138
				'call' => 'sqlite',
139
				'name' => 'SQLite',
140
			],
141
			'mysql' => [
142
				'type' => 'pdo',
143
				'call' => 'mysql',
144
				'name' => 'MySQL/MariaDB',
145
			],
146
			'pgsql' => [
147
				'type' => 'pdo',
148
				'call' => 'pgsql',
149
				'name' => 'PostgreSQL',
150
			],
151
			'oci' => [
152
				'type' => 'function',
153
				'call' => 'oci_connect',
154
				'name' => 'Oracle',
155
			],
156
		];
157
		if ($allowAllDatabases) {
158
			$configuredDatabases = array_keys($availableDatabases);
159
		} else {
160
			$configuredDatabases = $this->config->getValue('supportedDatabases',
161
				['sqlite', 'mysql', 'pgsql']);
162
		}
163
		if(!is_array($configuredDatabases)) {
164
			throw new Exception('Supported databases are not properly configured.');
165
		}
166
167
		$supportedDatabases = array();
168
169
		foreach($configuredDatabases as $database) {
170
			if(array_key_exists($database, $availableDatabases)) {
171
				$working = false;
172
				$type = $availableDatabases[$database]['type'];
173
				$call = $availableDatabases[$database]['call'];
174
175
				if ($type === 'function') {
176
					$working = $this->is_callable($call);
177
				} elseif($type === 'pdo') {
178
					$working = in_array($call, $this->getAvailableDbDriversForPdo(), true);
179
				}
180
				if($working) {
181
					$supportedDatabases[$database] = $availableDatabases[$database]['name'];
182
				}
183
			}
184
		}
185
186
		return $supportedDatabases;
187
	}
188
189
	/**
190
	 * Gathers system information like database type and does
191
	 * a few system checks.
192
	 *
193
	 * @return array of system info, including an "errors" value
194
	 * in case of errors/warnings
195
	 */
196
	public function getSystemInfo($allowAllDatabases = false) {
197
		$databases = $this->getSupportedDatabases($allowAllDatabases);
198
199
		$dataDir = $this->config->getValue('datadirectory', \OC::$SERVERROOT.'/data');
200
201
		$errors = [];
202
203
		// Create data directory to test whether the .htaccess works
204
		// Notice that this is not necessarily the same data directory as the one
205
		// that will effectively be used.
206
		if(!file_exists($dataDir)) {
207
			@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...
208
		}
209
		$htAccessWorking = true;
210
		if (is_dir($dataDir) && is_writable($dataDir)) {
211
			// Protect data directory here, so we can test if the protection is working
212
			self::protectDataDirectory();
213
214
			try {
215
				$util = new \OC_Util();
216
				$htAccessWorking = $util->isHtaccessWorking(\OC::$server->getConfig());
217
			} catch (\OC\HintException $e) {
218
				$errors[] = [
219
					'error' => $e->getMessage(),
220
					'hint' => $e->getHint(),
221
				];
222
				$htAccessWorking = false;
223
			}
224
		}
225
226
		if (\OC_Util::runningOnMac()) {
227
			$errors[] = [
228
				'error' => $this->l10n->t(
229
					'Mac OS X is not supported and %s will not work properly on this platform. ' .
230
					'Use it at your own risk! ',
231
					[$this->defaults->getName()]
232
				),
233
				'hint' => $this->l10n->t('For the best results, please consider using a GNU/Linux server instead.'),
234
			];
235
		}
236
237
		if($this->iniWrapper->getString('open_basedir') !== '' && PHP_INT_SIZE === 4) {
238
			$errors[] = [
239
				'error' => $this->l10n->t(
240
					'It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. ' .
241
					'This will lead to problems with files over 4 GB and is highly discouraged.',
242
					[$this->defaults->getName()]
243
				),
244
				'hint' => $this->l10n->t('Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP.'),
245
			];
246
		}
247
248
		return array(
249
			'hasSQLite' => isset($databases['sqlite']),
250
			'hasMySQL' => isset($databases['mysql']),
251
			'hasPostgreSQL' => isset($databases['pgsql']),
252
			'hasOracle' => isset($databases['oci']),
253
			'databases' => $databases,
254
			'directory' => $dataDir,
255
			'htaccessWorking' => $htAccessWorking,
256
			'errors' => $errors,
257
		);
258
	}
259
260
	/**
261
	 * @param $options
262
	 * @return array
263
	 */
264
	public function install($options) {
265
		$l = $this->l10n;
266
267
		$error = array();
268
		$dbType = $options['dbtype'];
269
270
		if(empty($options['adminlogin'])) {
271
			$error[] = $l->t('Set an admin username.');
272
		}
273
		if(empty($options['adminpass'])) {
274
			$error[] = $l->t('Set an admin password.');
275
		}
276
		if(empty($options['directory'])) {
277
			$options['directory'] = \OC::$SERVERROOT."/data";
278
		}
279
280
		if (!isset(self::$dbSetupClasses[$dbType])) {
281
			$dbType = 'sqlite';
282
		}
283
284
		$username = htmlspecialchars_decode($options['adminlogin']);
285
		$password = htmlspecialchars_decode($options['adminpass']);
286
		$dataDir = htmlspecialchars_decode($options['directory']);
287
288
		$class = self::$dbSetupClasses[$dbType];
289
		/** @var \OC\Setup\AbstractDatabase $dbSetup */
290
		$dbSetup = new $class($l, $this->config, $this->logger, $this->random);
291
		$error = array_merge($error, $dbSetup->validate($options));
292
293
		// validate the data directory
294
		if ((!is_dir($dataDir) && !mkdir($dataDir)) || !is_writable($dataDir)) {
295
			$error[] = $l->t("Can't create or write into the data directory %s", array($dataDir));
296
		}
297
298
		if (!empty($error)) {
299
			return $error;
300
		}
301
302
		$request = \OC::$server->getRequest();
303
304
		//no errors, good
305
		if(isset($options['trusted_domains'])
306
		    && is_array($options['trusted_domains'])) {
307
			$trustedDomains = $options['trusted_domains'];
308
		} else {
309
			$trustedDomains = [$request->getInsecureServerHost()];
310
		}
311
312
		//use sqlite3 when available, otherwise sqlite2 will be used.
313
		if ($dbType === 'sqlite' && class_exists('SQLite3')) {
314
			$dbType = 'sqlite3';
315
		}
316
317
		//generate a random salt that is used to salt the local user passwords
318
		$salt = $this->random->generate(30);
319
		// generate a secret
320
		$secret = $this->random->generate(48);
321
322
		//write the config file
323
		$this->config->setValues([
324
			'passwordsalt'		=> $salt,
325
			'secret'			=> $secret,
326
			'trusted_domains'	=> $trustedDomains,
327
			'datadirectory'		=> $dataDir,
328
			'overwrite.cli.url'	=> $request->getServerProtocol() . '://' . $request->getInsecureServerHost() . \OC::$WEBROOT,
329
			'dbtype'			=> $dbType,
330
			'version'			=> implode('.', \OCP\Util::getVersion()),
331
		]);
332
333
		try {
334
			$dbSetup->initialize($options);
335
			$dbSetup->setupDatabase($username);
336
			// apply necessary migrations
337
			$dbSetup->runMigrations();
338
		} catch (\OC\DatabaseSetupException $e) {
339
			$error[] = [
340
				'error' => $e->getMessage(),
341
				'hint' => $e->getHint(),
342
			];
343
			return $error;
344
		} catch (Exception $e) {
345
			$error[] = [
346
				'error' => 'Error while trying to create admin user: ' . $e->getMessage(),
347
				'hint' => '',
348
			];
349
			return $error;
350
		}
351
352
		//create the user and group
353
		$user =  null;
354
		try {
355
			$user = \OC::$server->getUserManager()->createUser($username, $password);
356
			if (!$user) {
357
				$error[] = "User <$username> could not be created.";
358
			}
359
		} catch(Exception $exception) {
360
			$error[] = $exception->getMessage();
361
		}
362
363
		if (empty($error)) {
364
			$config = \OC::$server->getConfig();
365
			$config->setAppValue('core', 'installedat', microtime(true));
366
			$config->setAppValue('core', 'lastupdatedat', microtime(true));
367
			$config->setAppValue('core', 'vendor', $this->getVendor());
368
369
			$group =\OC::$server->getGroupManager()->createGroup('admin');
370
			$group->addUser($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by \OC::$server->getUserMan...r($username, $password) on line 355 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...
371
372
			// Install shipped apps and specified app bundles
373
			Installer::installShippedApps();
374
			$installer = new Installer(
375
				\OC::$server->getAppFetcher(),
376
				\OC::$server->getHTTPClientService(),
377
				\OC::$server->getTempManager(),
378
				\OC::$server->getLogger(),
379
				\OC::$server->getConfig()
380
			);
381
			$bundleFetcher = new BundleFetcher(\OC::$server->getL10N('lib'));
382
			$defaultInstallationBundles = $bundleFetcher->getDefaultInstallationBundle();
383
			foreach($defaultInstallationBundles as $bundle) {
384
				try {
385
					$installer->installAppBundle($bundle);
386
				} catch (Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
387
			}
388
389
			// create empty file in data dir, so we can later find
390
			// out that this is indeed an ownCloud data directory
391
			file_put_contents($config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').'/.ocdata', '');
392
393
			// Update .htaccess files
394
			self::updateHtaccess();
395
			self::protectDataDirectory();
396
397
			self::installBackgroundJobs();
398
399
			//and we are done
400
			$config->setSystemValue('installed', true);
401
402
			// Create a session token for the newly created user
403
			// The token provider requires a working db, so it's not injected on setup
404
			/* @var $userSession User\Session */
405
			$userSession = \OC::$server->getUserSession();
406
			$defaultTokenProvider = \OC::$server->query(DefaultTokenProvider::class);
407
			$userSession->setTokenProvider($defaultTokenProvider);
408
			$userSession->login($username, $password);
409
			$userSession->createSessionToken($request, $userSession->getUser()->getUID(), $username, $password);
410
		}
411
412
		return $error;
413
	}
414
415
	public static function installBackgroundJobs() {
416
		$jobList = \OC::$server->getJobList();
417
		$jobList->add(DefaultTokenCleanupJob::class);
418
		$jobList->add(Rotate::class);
419
	}
420
421
	/**
422
	 * @return string Absolute path to htaccess
423
	 */
424
	private function pathToHtaccess() {
425
		return \OC::$SERVERROOT.'/.htaccess';
426
	}
427
428
	/**
429
	 * Append the correct ErrorDocument path for Apache hosts
430
	 * @return bool True when success, False otherwise
431
	 */
432
	public static function updateHtaccess() {
433
		$config = \OC::$server->getSystemConfig();
434
435
		// For CLI read the value from overwrite.cli.url
436
		if(\OC::$CLI) {
437
			$webRoot = $config->getValue('overwrite.cli.url', '');
438
			if($webRoot === '') {
439
				return false;
440
			}
441
			$webRoot = parse_url($webRoot, PHP_URL_PATH);
442
			$webRoot = rtrim($webRoot, '/');
443
		} else {
444
			$webRoot = !empty(\OC::$WEBROOT) ? \OC::$WEBROOT : '/';
445
		}
446
447
		$setupHelper = new \OC\Setup($config, \OC::$server->getIniWrapper(),
448
			\OC::$server->getL10N('lib'), \OC::$server->query(Defaults::class), \OC::$server->getLogger(),
449
			\OC::$server->getSecureRandom());
450
451
		$htaccessContent = file_get_contents($setupHelper->pathToHtaccess());
452
		$content = "#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####\n";
453
		$htaccessContent = explode($content, $htaccessContent, 2)[0];
454
455
		//custom 403 error page
456
		$content.= "\nErrorDocument 403 ".$webRoot."/";
457
458
		//custom 404 error page
459
		$content.= "\nErrorDocument 404 ".$webRoot."/";
460
461
		// Add rewrite rules if the RewriteBase is configured
462
		$rewriteBase = $config->getValue('htaccess.RewriteBase', '');
463
		if($rewriteBase !== '') {
464
			$content .= "\n<IfModule mod_rewrite.c>";
465
			$content .= "\n  Options -MultiViews";
466
			$content .= "\n  RewriteRule ^core/js/oc.js$ index.php [PT,E=PATH_INFO:$1]";
467
			$content .= "\n  RewriteRule ^core/preview.png$ index.php [PT,E=PATH_INFO:$1]";
468
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !\\.(css|js|svg|gif|png|html|ttf|woff|ico|jpg|jpeg)$";
469
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !core/img/favicon.ico$";
470
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !core/img/manifest.json$";
471
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/remote.php";
472
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/public.php";
473
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/cron.php";
474
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/core/ajax/update.php";
475
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/status.php";
476
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/ocs/v1.php";
477
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/ocs/v2.php";
478
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/robots.txt";
479
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/updater/";
480
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/ocs-provider/";
481
			$content .= "\n  RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/.*";
482
			$content .= "\n  RewriteRule . index.php [PT,E=PATH_INFO:$1]";
483
			$content .= "\n  RewriteBase " . $rewriteBase;
484
			$content .= "\n  <IfModule mod_env.c>";
485
			$content .= "\n    SetEnv front_controller_active true";
486
			$content .= "\n    <IfModule mod_dir.c>";
487
			$content .= "\n      DirectorySlash off";
488
			$content .= "\n    </IfModule>";
489
			$content .= "\n  </IfModule>";
490
			$content .= "\n</IfModule>";
491
		}
492
493
		if ($content !== '') {
494
			//suppress errors in case we don't have permissions for it
495
			return (bool) @file_put_contents($setupHelper->pathToHtaccess(), $htaccessContent.$content . "\n");
496
		}
497
498
		return false;
499
	}
500
501
	public static function protectDataDirectory() {
502
		//Require all denied
503
		$now =  date('Y-m-d H:i:s');
504
		$content = "# Generated by Nextcloud on $now\n";
505
		$content.= "# line below if for Apache 2.4\n";
506
		$content.= "<ifModule mod_authz_core.c>\n";
507
		$content.= "Require all denied\n";
508
		$content.= "</ifModule>\n\n";
509
		$content.= "# line below if for Apache 2.2\n";
510
		$content.= "<ifModule !mod_authz_core.c>\n";
511
		$content.= "deny from all\n";
512
		$content.= "Satisfy All\n";
513
		$content.= "</ifModule>\n\n";
514
		$content.= "# section for Apache 2.2 and 2.4\n";
515
		$content.= "<ifModule mod_autoindex.c>\n";
516
		$content.= "IndexIgnore *\n";
517
		$content.= "</ifModule>\n";
518
519
		$baseDir = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data');
520
		file_put_contents($baseDir . '/.htaccess', $content);
521
		file_put_contents($baseDir . '/index.html', '');
522
	}
523
524
	/**
525
	 * Return vendor from which this version was published
526
	 *
527
	 * @return string Get the vendor
528
	 *
529
	 * Copy of \OC\Updater::getVendor()
530
	 */
531
	private function getVendor() {
532
		// this should really be a JSON file
533
		require \OC::$SERVERROOT . '/version.php';
534
		/** @var string $vendor */
535
		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...
536
	}
537
}
538