Completed
Push — master ( 0a6e09...80a2bc )
by Thomas
12:18
created

Setup::IsClassExisting()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
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 Christoph Wurst <[email protected]>
9
 * @author François Kubler <[email protected]>
10
 * @author Jakob Sack <[email protected]>
11
 * @author Joas Schilling <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Martin Mattel <[email protected]>
14
 * @author Morris Jobke <[email protected]>
15
 * @author Robin Appelman <[email protected]>
16
 * @author Roeland Jago Douma <[email protected]>
17
 * @author Sean Comeau <[email protected]>
18
 * @author Serge Martin <[email protected]>
19
 * @author Thomas Müller <[email protected]>
20
 * @author Vincent Petry <[email protected]>
21
 *
22
 * @copyright Copyright (c) 2016, ownCloud GmbH.
23
 * @license AGPL-3.0
24
 *
25
 * This code is free software: you can redistribute it and/or modify
26
 * it under the terms of the GNU Affero General Public License, version 3,
27
 * as published by the Free Software Foundation.
28
 *
29
 * This program is distributed in the hope that it will be useful,
30
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32
 * GNU Affero General Public License for more details.
33
 *
34
 * You should have received a copy of the GNU Affero General Public License, version 3,
35
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
36
 *
37
 */
38
39
namespace OC;
40
41
use bantu\IniGetWrapper\IniGetWrapper;
42
use Exception;
43
use OCP\IConfig;
44
use OCP\IL10N;
45
use OCP\ILogger;
46
use OCP\Security\ISecureRandom;
47
48
class Setup {
49
	/** @var \OCP\IConfig */
50
	protected $config;
51
	/** @var IniGetWrapper */
52
	protected $iniWrapper;
53
	/** @var IL10N */
54
	protected $l10n;
55
	/** @var \OC_Defaults */
56
	protected $defaults;
57
	/** @var ILogger */
58
	protected $logger;
59
	/** @var ISecureRandom */
60
	protected $random;
61
62
	/**
63
	 * @param IConfig $config
64
	 * @param IniGetWrapper $iniWrapper
65
	 * @param \OC_Defaults $defaults
66
	 */
67
	function __construct(IConfig $config,
68
						 IniGetWrapper $iniWrapper,
69
						 IL10N $l10n,
70
						 \OC_Defaults $defaults,
71
						 ILogger $logger,
72
						 ISecureRandom $random
73
		) {
74
		$this->config = $config;
75
		$this->iniWrapper = $iniWrapper;
76
		$this->l10n = $l10n;
77
		$this->defaults = $defaults;
78
		$this->logger = $logger;
79
		$this->random = $random;
80
	}
81
82
	static $dbSetupClasses = array(
83
		'mysql' => '\OC\Setup\MySQL',
84
		'pgsql' => '\OC\Setup\PostgreSQL',
85
		'oci'   => '\OC\Setup\OCI',
86
		'sqlite' => '\OC\Setup\Sqlite',
87
		'sqlite3' => '\OC\Setup\Sqlite',
88
	);
89
90
	/**
91
	 * Wrapper around the "class_exists" PHP function to be able to mock it
92
	 * @param string $name
93
	 * @return bool
94
	 */
95
	protected function IsClassExisting($name) {
96
		return class_exists($name);
97
	}
98
99
	/**
100
	 * Wrapper around the "is_callable" PHP function to be able to mock it
101
	 * @param string $name
102
	 * @return bool
103
	 */
104
	protected function is_callable($name) {
105
		return is_callable($name);
106
	}
107
108
	/**
109
	 * Wrapper around \PDO::getAvailableDrivers
110
	 *
111
	 * @return array
112
	 */
113
	protected function getAvailableDbDriversForPdo() {
114
		return \PDO::getAvailableDrivers();
115
	}
116
117
	/**
118
	 * Get the available and supported databases of this instance
119
	 *
120
	 * @param bool $allowAllDatabases
121
	 * @return array
122
	 * @throws Exception
123
	 */
124
	public function getSupportedDatabases($allowAllDatabases = false) {
125
		$availableDatabases = array(
126
			'sqlite' =>  array(
127
				'type' => 'class',
128
				'call' => 'SQLite3',
129
				'name' => 'SQLite'
130
			),
131
			'mysql' => array(
132
				'type' => 'pdo',
133
				'call' => 'mysql',
134
				'name' => 'MySQL/MariaDB'
135
			),
136
			'pgsql' => array(
137
				'type' => 'function',
138
				'call' => 'pg_connect',
139
				'name' => 'PostgreSQL'
140
			),
141
			'oci' => array(
142
				'type' => 'function',
143
				'call' => 'oci_connect',
144
				'name' => 'Oracle'
145
			)
146
		);
147
		if ($allowAllDatabases) {
148
			$configuredDatabases = array_keys($availableDatabases);
149
		} else {
150
			$configuredDatabases = $this->config->getSystemValue('supportedDatabases',
151
				array('sqlite', 'mysql', 'pgsql'));
152
		}
153
		if(!is_array($configuredDatabases)) {
154
			throw new Exception('Supported databases are not properly configured.');
155
		}
156
157
		$supportedDatabases = array();
158
159
		foreach($configuredDatabases as $database) {
160
			if(array_key_exists($database, $availableDatabases)) {
161
				$working = false;
162
				$type = $availableDatabases[$database]['type'];
163
				$call = $availableDatabases[$database]['call'];
164
165
				if($type === 'class') {
166
					$working = $this->IsClassExisting($call);
167
				} elseif ($type === 'function') {
168
					$working = $this->is_callable($call);
169
				} elseif($type === 'pdo') {
170
					$working = in_array($call, $this->getAvailableDbDriversForPdo(), TRUE);
171
				}
172
				if($working) {
173
					$supportedDatabases[$database] = $availableDatabases[$database]['name'];
174
				}
175
			}
176
		}
177
178
		return $supportedDatabases;
179
	}
180
181
	/**
182
	 * Gathers system information like database type and does
183
	 * a few system checks.
184
	 *
185
	 * @return array of system info, including an "errors" value
186
	 * in case of errors/warnings
187
	 */
188
	public function getSystemInfo($allowAllDatabases = false) {
189
		$databases = $this->getSupportedDatabases($allowAllDatabases);
190
191
		$dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data');
192
193
		$errors = array();
194
195
		// Create data directory to test whether the .htaccess works
196
		// Notice that this is not necessarily the same data directory as the one
197
		// that will effectively be used.
198
		if(!file_exists($dataDir)) {
199
			@mkdir($dataDir);
200
		}
201
		$htAccessWorking = true;
202
		if (is_dir($dataDir) && is_writable($dataDir)) {
203
			// Protect data directory here, so we can test if the protection is working
204
			\OC\Setup::protectDataDirectory();
205
206
			try {
207
				$util = new \OC_Util();
208
				$htAccessWorking = $util->isHtaccessWorking(\OC::$server->getConfig());
209
			} catch (\OC\HintException $e) {
210
				$errors[] = array(
211
					'error' => $e->getMessage(),
212
					'hint' => $e->getHint()
213
				);
214
				$htAccessWorking = false;
215
			}
216
		}
217
218
		if (\OC_Util::runningOnMac()) {
219
			$errors[] = array(
220
				'error' => $this->l10n->t(
221
					'Mac OS X is not supported and %s will not work properly on this platform. ' .
222
					'Use it at your own risk! ',
223
					$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...
224
				),
225
				'hint' => $this->l10n->t('For the best results, please consider using a GNU/Linux server instead.')
226
			);
227
		}
228
229
		if($this->iniWrapper->getString('open_basedir') !== '' && PHP_INT_SIZE === 4) {
230
			$errors[] = array(
231
				'error' => $this->l10n->t(
232
					'It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. ' .
233
					'This will lead to problems with files over 4 GB and is highly discouraged.',
234
					$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...
235
				),
236
				'hint' => $this->l10n->t('Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP.')
237
			);
238
		}
239
240
		return array(
241
			'hasSQLite' => isset($databases['sqlite']),
242
			'hasMySQL' => isset($databases['mysql']),
243
			'hasPostgreSQL' => isset($databases['pgsql']),
244
			'hasOracle' => isset($databases['oci']),
245
			'databases' => $databases,
246
			'directory' => $dataDir,
247
			'htaccessWorking' => $htAccessWorking,
248
			'errors' => $errors,
249
		);
250
	}
251
252
	/**
253
	 * @param $options
254
	 * @return array
255
	 */
256
	public function install($options) {
257
		$l = $this->l10n;
258
259
		$error = array();
260
		$dbType = $options['dbtype'];
261
262
		if(empty($options['adminlogin'])) {
263
			$error[] = $l->t('Set an admin username.');
264
		}
265
		if(empty($options['adminpass'])) {
266
			$error[] = $l->t('Set an admin password.');
267
		}
268
		if(empty($options['directory'])) {
269
			$options['directory'] = \OC::$SERVERROOT."/data";
270
		}
271
272
		if (!isset(self::$dbSetupClasses[$dbType])) {
273
			$dbType = 'sqlite';
274
		}
275
276
		$username = htmlspecialchars_decode($options['adminlogin']);
277
		$password = htmlspecialchars_decode($options['adminpass']);
278
		$dataDir = htmlspecialchars_decode($options['directory']);
279
280
		$class = self::$dbSetupClasses[$dbType];
281
		/** @var \OC\Setup\AbstractDatabase $dbSetup */
282
		$dbSetup = new $class($l, 'db_structure.xml', $this->config,
283
			$this->logger, $this->random);
284
		$error = array_merge($error, $dbSetup->validate($options));
285
286
		// validate the data directory
287
		if (
288
			(!is_dir($dataDir) and !mkdir($dataDir)) or
289
			!is_writable($dataDir)
290
		) {
291
			$error[] = $l->t("Can't create or write into the data directory %s", array($dataDir));
292
		}
293
294
		if(count($error) != 0) {
295
			return $error;
296
		}
297
298
		$request = \OC::$server->getRequest();
299
300
		//no errors, good
301
		if(isset($options['trusted_domains'])
302
		    && is_array($options['trusted_domains'])) {
303
			$trustedDomains = $options['trusted_domains'];
304
		} else {
305
			$trustedDomains = [$request->getInsecureServerHost()];
306
		}
307
308
		if (\OC_Util::runningOnWindows()) {
309
			$dataDir = rtrim(realpath($dataDir), '\\');
310
		}
311
312
		//use sqlite3 when available, otherwise sqlite2 will be used.
313
		if($dbType=='sqlite' and $this->IsClassExisting('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->setSystemValues([
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
		} catch (\OC\DatabaseSetupException $e) {
337
			$error[] = array(
338
				'error' => $e->getMessage(),
339
				'hint' => $e->getHint()
340
			);
341
			return($error);
342
		} catch (Exception $e) {
343
			$error[] = array(
344
				'error' => 'Error while trying to create admin user: ' . $e->getMessage(),
345
				'hint' => ''
346
			);
347
			return($error);
348
		}
349
350
		//create the user and group
351
		$user =  null;
352
		try {
353
			$user = \OC::$server->getUserManager()->createUser($username, $password);
354
			if (!$user) {
355
				$error[] = "User <$username> could not be created.";
356
			}
357
		} catch(Exception $exception) {
358
			$error[] = $exception->getMessage();
359
		}
360
361
		if(count($error) == 0) {
362
			$config = \OC::$server->getConfig();
363
			$config->setAppValue('core', 'installedat', microtime(true));
364
			$config->setAppValue('core', 'lastupdatedat', microtime(true));
365
366
			$group =\OC::$server->getGroupManager()->createGroup('admin');
367
			$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 353 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...
368
369
			// Create a session token for the newly created user
370
			// The token provider requires a working db, so it's not injected on setup
371
			/* @var $userSession User\Session */
372
			$userSession = \OC::$server->getUserSession();
373
			$defaultTokenProvider = \OC::$server->query('OC\Authentication\Token\DefaultTokenProvider');
374
			$userSession->setTokenProvider($defaultTokenProvider);
375
			$userSession->login($username, $password);
376
			$userSession->createSessionToken($request, $userSession->getUser()->getUID(), $username, $password);
377
378
			//guess what this does
379
			Installer::installShippedApps();
380
381
			// create empty file in data dir, so we can later find
382
			// out that this is indeed an ownCloud data directory
383
			file_put_contents($config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').'/.ocdata', '');
384
385
			// Update .htaccess files
386
			Setup::updateHtaccess();
387
			Setup::protectDataDirectory();
388
389
			//try to write logtimezone
390
			if (date_default_timezone_get()) {
391
				$config->setSystemValue('logtimezone', date_default_timezone_get());
392
			}
393
394
			self::installBackgroundJobs();
395
396
			//and we are done
397
			$config->setSystemValue('installed', true);
398
		}
399
400
		return $error;
401
	}
402
403
	public static function installBackgroundJobs() {
404
		\OC::$server->getJobList()->add('\OC\Authentication\Token\DefaultTokenCleanupJob');
405
	}
406
407
	/**
408
	 * @return string Absolute path to htaccess
409
	 */
410
	private function pathToHtaccess() {
411
		return \OC::$SERVERROOT.'/.htaccess';
412
	}
413
414
	/**
415
	 * Append the correct ErrorDocument path for Apache hosts
416
	 */
417
	public static function updateHtaccess() {
418
		$config = \OC::$server->getConfig();
419
420
		// For CLI read the value from overwrite.cli.url
421
		if(\OC::$CLI) {
422
			$webRoot = $config->getSystemValue('overwrite.cli.url', '');
423
			if($webRoot === '') {
424
				return;
425
			}
426
			$webRoot = parse_url($webRoot, PHP_URL_PATH);
427
			$webRoot = rtrim($webRoot, '/');
428
		} else {
429
			$webRoot = !empty(\OC::$WEBROOT) ? \OC::$WEBROOT : '/';
430
		}
431
432
		$setupHelper = new \OC\Setup($config, \OC::$server->getIniWrapper(),
433
			\OC::$server->getL10N('lib'), new \OC_Defaults(), \OC::$server->getLogger(),
434
			\OC::$server->getSecureRandom());
435
436
		$htaccessContent = file_get_contents($setupHelper->pathToHtaccess());
437
		$content = "#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####\n";
438
		$htaccessContent = explode($content, $htaccessContent, 2)[0];
439
440
		//custom 403 error page
441
		$content.= "\nErrorDocument 403 ".$webRoot."/core/templates/403.php";
442
443
		//custom 404 error page
444
		$content.= "\nErrorDocument 404 ".$webRoot."/core/templates/404.php";
445
446
		// Add rewrite rules if the RewriteBase is configured
447
		$rewriteBase = $config->getSystemValue('htaccess.RewriteBase', '');
448
		if($rewriteBase !== '') {
449
			$content .= "\n<IfModule mod_rewrite.c>";
450
			$content .= "\n  Options -MultiViews";
451
			$content .= "\n  RewriteRule ^core/js/oc.js$ index.php [PT,E=PATH_INFO:$1]";
452
			$content .= "\n  RewriteRule ^core/preview.png$ index.php [PT,E=PATH_INFO:$1]";
453
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !\\.(css|js|svg|gif|png|html|ttf|woff|ico|jpg|jpeg)$";
454
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !core/img/favicon.ico$";
455
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/remote.php";
456
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/public.php";
457
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/cron.php";
458
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/core/ajax/update.php";
459
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/status.php";
460
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/ocs/v1.php";
461
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/ocs/v2.php";
462
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/updater/";
463
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/ocs-provider/";
464
			$content .= "\n  RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/.*";
465
			$content .= "\n  RewriteRule . index.php [PT,E=PATH_INFO:$1]";
466
			$content .= "\n  RewriteBase " . $rewriteBase;
467
			$content .= "\n  <IfModule mod_env.c>";
468
			$content .= "\n    SetEnv front_controller_active true";
469
			$content .= "\n    <IfModule mod_dir.c>";
470
			$content .= "\n      DirectorySlash off";
471
			$content .= "\n    </IfModule>";
472
			$content .= "\n  </IfModule>";
473
			$content .= "\n</IfModule>";
474
		}
475
476
		if ($content !== '') {
477
			//suppress errors in case we don't have permissions for it
478
			@file_put_contents($setupHelper->pathToHtaccess(), $htaccessContent.$content . "\n");
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...
479
		}
480
481
	}
482
483
	public static function protectDataDirectory() {
484
		//Require all denied
485
		$now =  date('Y-m-d H:i:s');
486
		$content = "# Generated by ownCloud on $now\n";
487
		$content.= "# line below if for Apache 2.4\n";
488
		$content.= "<ifModule mod_authz_core.c>\n";
489
		$content.= "Require all denied\n";
490
		$content.= "</ifModule>\n\n";
491
		$content.= "# line below if for Apache 2.2\n";
492
		$content.= "<ifModule !mod_authz_core.c>\n";
493
		$content.= "deny from all\n";
494
		$content.= "Satisfy All\n";
495
		$content.= "</ifModule>\n\n";
496
		$content.= "# section for Apache 2.2 and 2.4\n";
497
		$content.= "IndexIgnore *\n";
498
499
		$baseDir = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data');
500
		file_put_contents($baseDir . '/.htaccess', $content);
501
		file_put_contents($baseDir . '/index.html', '');
502
	}
503
}
504