Completed
Pull Request — stable8.2 (#24656)
by Joas
12:22
created

Setup::install()   F

Complexity

Conditions 22
Paths 6176

Size

Total Lines 138
Code Lines 81

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 506
Metric Value
dl 0
loc 138
ccs 0
cts 92
cp 0
rs 2
cc 22
eloc 81
nc 6176
nop 1
crap 506

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
219
				),
220
				'hint' => $this->l10n->t('For the best results, please consider using a GNU/Linux server instead.')
221
			);
222
		}
223
224
		if($this->iniWrapper->getString('open_basedir') !== '' && PHP_INT_SIZE === 4) {
225
			$errors[] = array(
226
				'error' => $this->l10n->t(
227
					'It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. ' .
228
					'This will lead to problems with files over 4 GB and is highly discouraged.',
229
					$this->defaults->getName()
0 ignored issues
show
Documentation introduced by
$this->defaults->getName() is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
230
				),
231
				'hint' => $this->l10n->t('Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP.')
232
			);
233
		}
234
235
		return array(
236
			'hasSQLite' => isset($databases['sqlite']),
237
			'hasMySQL' => isset($databases['mysql']),
238
			'hasPostgreSQL' => isset($databases['pgsql']),
239
			'hasOracle' => isset($databases['oci']),
240
			'databases' => $databases,
241
			'directory' => $dataDir,
242
			'htaccessWorking' => $htAccessWorking,
243
			'errors' => $errors,
244
		);
245
	}
246
247
	/**
248
	 * @param $options
249
	 * @return array
250
	 */
251
	public function install($options) {
252
		$l = $this->l10n;
253
254
		$error = array();
255
		$dbType = $options['dbtype'];
256
257
		if(empty($options['adminlogin'])) {
258
			$error[] = $l->t('Set an admin username.');
259
		}
260
		if(empty($options['adminpass'])) {
261
			$error[] = $l->t('Set an admin password.');
262
		}
263
		if(empty($options['directory'])) {
264
			$options['directory'] = \OC::$SERVERROOT."/data";
265
		}
266
267
		if (!isset(self::$dbSetupClasses[$dbType])) {
268
			$dbType = 'sqlite';
269
		}
270
271
		$username = htmlspecialchars_decode($options['adminlogin']);
272
		$password = htmlspecialchars_decode($options['adminpass']);
273
		$dataDir = htmlspecialchars_decode($options['directory']);
274
275
		$class = self::$dbSetupClasses[$dbType];
276
		/** @var \OC\Setup\AbstractDatabase $dbSetup */
277
		$dbSetup = new $class($l, 'db_structure.xml', $this->config,
278
			$this->logger, $this->random);
279
		$error = array_merge($error, $dbSetup->validate($options));
280
281
		// validate the data directory
282
		if (
283
			(!is_dir($dataDir) and !mkdir($dataDir)) or
284
			!is_writable($dataDir)
285
		) {
286
			$error[] = $l->t("Can't create or write into the data directory %s", array($dataDir));
287
		}
288
289
		if(count($error) != 0) {
290
			return $error;
291
		}
292
293
		$request = \OC::$server->getRequest();
294
295
		//no errors, good
296
		if(isset($options['trusted_domains'])
297
		    && is_array($options['trusted_domains'])) {
298
			$trustedDomains = $options['trusted_domains'];
299
		} else {
300
			$trustedDomains = [$request->getInsecureServerHost()];
301
		}
302
303
		if (\OC_Util::runningOnWindows()) {
304
			$dataDir = rtrim(realpath($dataDir), '\\');
305
		}
306
307
		//use sqlite3 when available, otherwise sqlite2 will be used.
308
		if($dbType=='sqlite' and class_exists('SQLite3')) {
309
			$dbType='sqlite3';
310
		}
311
312
		//generate a random salt that is used to salt the local user passwords
313
		$salt = $this->random->getLowStrengthGenerator()->generate(30);
314
		// generate a secret
315
		$secret = $this->random->getMediumStrengthGenerator()->generate(48);
316
317
		//write the config file
318
		$this->config->setSystemValues([
319
			'passwordsalt'		=> $salt,
320
			'secret'			=> $secret,
321
			'trusted_domains'	=> $trustedDomains,
322
			'datadirectory'		=> $dataDir,
323
			'overwrite.cli.url'	=> $request->getServerProtocol() . '://' . $request->getInsecureServerHost() . \OC::$WEBROOT,
324
			'dbtype'			=> $dbType,
325
			'version'			=> implode('.', \OC_Util::getVersion()),
326
		]);
327
328
		try {
329
			$dbSetup->initialize($options);
330
			$dbSetup->setupDatabase($username);
331
		} catch (\OC\DatabaseSetupException $e) {
332
			$error[] = array(
333
				'error' => $e->getMessage(),
334
				'hint' => $e->getHint()
335
			);
336
			return($error);
337
		} catch (Exception $e) {
338
			$error[] = array(
339
				'error' => 'Error while trying to create admin user: ' . $e->getMessage(),
340
				'hint' => ''
341
			);
342
			return($error);
343
		}
344
345
		//create the user and group
346
		$user =  null;
347
		try {
348
			$user = \OC::$server->getUserManager()->createUser($username, $password);
349
			if (!$user) {
350
				$error[] = "User <$username> could not be created.";
351
			}
352
		} catch(Exception $exception) {
353
			$error[] = $exception->getMessage();
354
		}
355
356
		if(count($error) == 0) {
357
			$config = \OC::$server->getConfig();
358
			$config->setAppValue('core', 'installedat', microtime(true));
359
			$config->setAppValue('core', 'lastupdatedat', microtime(true));
360
361
			$group =\OC::$server->getGroupManager()->createGroup('admin');
362
			$group->addUser($user);
0 ignored issues
show
Security Bug introduced by
It seems like $user defined by \OC::$server->getUserMan...r($username, $password) on line 348 can also be of type false; however, OC\Group\Group::addUser() does only seem to accept object<OC\User\User>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
363
			\OC_User::login($username, $password);
364
365
			//guess what this does
366
			\OC_Installer::installShippedApps();
367
368
			// create empty file in data dir, so we can later find
369
			// out that this is indeed an ownCloud data directory
370
			file_put_contents($config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').'/.ocdata', '');
371
372
			// Update htaccess files for apache hosts
373
			if (isset($_SERVER['SERVER_SOFTWARE']) && strstr($_SERVER['SERVER_SOFTWARE'], 'Apache')) {
374
				self::updateHtaccess();
375
				self::protectDataDirectory();
376
			}
377
378
			//try to write logtimezone
379
			if (date_default_timezone_get()) {
380
				$config->setSystemValue('logtimezone', date_default_timezone_get());
381
			}
382
383
			//and we are done
384
			$config->setSystemValue('installed', true);
385
		}
386
387
		return $error;
388
	}
389
390
	/**
391
	 * @return string Absolute path to htaccess
392
	 */
393 1
	private function pathToHtaccess() {
394 1
		return \OC::$SERVERROOT.'/.htaccess';
395
	}
396
397
	/**
398
	 * Checks if the .htaccess contains the current version parameter
399
	 *
400
	 * @return bool
401
	 */
402 1
	private function isCurrentHtaccess() {
403 1
		$version = \OC_Util::getVersion();
404 1
		unset($version[3]);
405
406 1
		return !strpos(
407 1
			file_get_contents($this->pathToHtaccess()),
408 1
			'Version: '.implode('.', $version)
409 1
		) === false;
410
	}
411
412
	/**
413
	 * Append the correct ErrorDocument path for Apache hosts
414
	 *
415
	 * @throws \OC\HintException If .htaccess does not include the current version
416
	 */
417
	public static function updateHtaccess() {
418
		$setupHelper = new \OC\Setup(\OC::$server->getConfig(), \OC::$server->getIniWrapper(),
419
			\OC::$server->getL10N('lib'), new \OC_Defaults(), \OC::$server->getLogger(),
420
			\OC::$server->getSecureRandom());
421
		if(!$setupHelper->isCurrentHtaccess()) {
422
			throw new \OC\HintException('.htaccess file has the wrong version. Please upload the correct version. Maybe you forgot to replace it after updating?');
423
		}
424
425
		$htaccessContent = file_get_contents($setupHelper->pathToHtaccess());
426
		$content = '';
427 View Code Duplication
		if (strpos($htaccessContent, 'ErrorDocument 403') === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
428
			//custom 403 error page
429
			$content.= "\nErrorDocument 403 ".\OC::$WEBROOT."/core/templates/403.php";
430
		}
431 View Code Duplication
		if (strpos($htaccessContent, 'ErrorDocument 404') === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
432
			//custom 404 error page
433
			$content.= "\nErrorDocument 404 ".\OC::$WEBROOT."/core/templates/404.php";
434
		}
435
		if ($content !== '') {
436
			//suppress errors in case we don't have permissions for it
437
			@file_put_contents($setupHelper->pathToHtaccess(), $content . "\n", FILE_APPEND);
438
		}
439
	}
440
441
	public static function protectDataDirectory() {
442
		//Require all denied
443
		$now =  date('Y-m-d H:i:s');
444
		$content = "# Generated by ownCloud on $now\n";
445
		$content.= "# line below if for Apache 2.4\n";
446
		$content.= "<ifModule mod_authz_core.c>\n";
447
		$content.= "Require all denied\n";
448
		$content.= "</ifModule>\n\n";
449
		$content.= "# line below if for Apache 2.2\n";
450
		$content.= "<ifModule !mod_authz_core.c>\n";
451
		$content.= "deny from all\n";
452
		$content.= "Satisfy All\n";
453
		$content.= "</ifModule>\n\n";
454
		$content.= "# section for Apache 2.2 and 2.4\n";
455
		$content.= "IndexIgnore *\n";
456
		file_put_contents(\OC_Config::getValue('datadirectory', \OC::$SERVERROOT.'/data').'/.htaccess', $content);
457
		file_put_contents(\OC_Config::getValue('datadirectory', \OC::$SERVERROOT.'/data').'/index.html', '');
458
	}
459
}
460