Completed
Push — master ( a88fc0...4ea33f )
by Björn
08:20
created

Setup::is_callable()   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
 * @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 François Kubler <[email protected]>
12
 * @author Jakob Sack <[email protected]>
13
 * @author Joas Schilling <[email protected]>
14
 * @author Lukas Reschke <[email protected]>
15
 * @author Martin Mattel <[email protected]>
16
 * @author Morris Jobke <[email protected]>
17
 * @author Robin Appelman <[email protected]>
18
 * @author Roeland Jago Douma <[email protected]>
19
 * @author Sean Comeau <[email protected]>
20
 * @author Serge Martin <[email protected]>
21
 * @author Thomas Müller <[email protected]>
22
 * @author Vincent Petry <[email protected]>
23
 *
24
 * @license AGPL-3.0
25
 *
26
 * This code is free software: you can redistribute it and/or modify
27
 * it under the terms of the GNU Affero General Public License, version 3,
28
 * as published by the Free Software Foundation.
29
 *
30
 * This program is distributed in the hope that it will be useful,
31
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33
 * GNU Affero General Public License for more details.
34
 *
35
 * You should have received a copy of the GNU Affero General Public License, version 3,
36
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
37
 *
38
 */
39
40
namespace OC;
41
42
use bantu\IniGetWrapper\IniGetWrapper;
43
use Exception;
44
use OCP\IConfig;
45
use OCP\IL10N;
46
use OCP\ILogger;
47
use OCP\Security\ISecureRandom;
48
49
class Setup {
50
	/** @var \OCP\IConfig */
51
	protected $config;
52
	/** @var IniGetWrapper */
53
	protected $iniWrapper;
54
	/** @var IL10N */
55
	protected $l10n;
56
	/** @var \OC_Defaults */
57
	protected $defaults;
58
	/** @var ILogger */
59
	protected $logger;
60
	/** @var ISecureRandom */
61
	protected $random;
62
63
	/**
64
	 * @param IConfig $config
65
	 * @param IniGetWrapper $iniWrapper
66
	 * @param \OC_Defaults $defaults
67
	 */
68
	function __construct(IConfig $config,
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

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