Completed
Pull Request — stable10 (#3427)
by Morris
06:56
created

Setup::getVendor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 6
rs 9.4285
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' => 'class',
129
				'call' => 'SQLite3',
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 === 'class') {
167
					$working = $this->class_exists($call);
168
				} elseif ($type === 'function') {
169
					$working = $this->is_callable($call);
170
				} elseif($type === 'pdo') {
171
					$working = in_array($call, $this->getAvailableDbDriversForPdo(), TRUE);
172
				}
173
				if($working) {
174
					$supportedDatabases[$database] = $availableDatabases[$database]['name'];
175
				}
176
			}
177
		}
178
179
		return $supportedDatabases;
180
	}
181
182
	/**
183
	 * Gathers system information like database type and does
184
	 * a few system checks.
185
	 *
186
	 * @return array of system info, including an "errors" value
187
	 * in case of errors/warnings
188
	 */
189
	public function getSystemInfo($allowAllDatabases = false) {
190
		$databases = $this->getSupportedDatabases($allowAllDatabases);
191
192
		$dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data');
193
194
		$errors = array();
195
196
		// Create data directory to test whether the .htaccess works
197
		// Notice that this is not necessarily the same data directory as the one
198
		// that will effectively be used.
199
		if(!file_exists($dataDir)) {
200
			@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...
201
		}
202
		$htAccessWorking = true;
203
		if (is_dir($dataDir) && is_writable($dataDir)) {
204
			// Protect data directory here, so we can test if the protection is working
205
			\OC\Setup::protectDataDirectory();
206
207
			try {
208
				$util = new \OC_Util();
209
				$htAccessWorking = $util->isHtaccessWorking(\OC::$server->getConfig());
210
			} catch (\OC\HintException $e) {
211
				$errors[] = array(
212
					'error' => $e->getMessage(),
213
					'hint' => $e->getHint()
214
				);
215
				$htAccessWorking = false;
216
			}
217
		}
218
219
		if (\OC_Util::runningOnMac()) {
220
			$errors[] = array(
221
				'error' => $this->l10n->t(
222
					'Mac OS X is not supported and %s will not work properly on this platform. ' .
223
					'Use it at your own risk! ',
224
					$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...
225
				),
226
				'hint' => $this->l10n->t('For the best results, please consider using a GNU/Linux server instead.')
227
			);
228
		}
229
230
		if($this->iniWrapper->getString('open_basedir') !== '' && PHP_INT_SIZE === 4) {
231
			$errors[] = array(
232
				'error' => $this->l10n->t(
233
					'It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. ' .
234
					'This will lead to problems with files over 4 GB and is highly discouraged.',
235
					$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...
236
				),
237
				'hint' => $this->l10n->t('Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP.')
238
			);
239
		}
240
241
		return array(
242
			'hasSQLite' => isset($databases['sqlite']),
243
			'hasMySQL' => isset($databases['mysql']),
244
			'hasPostgreSQL' => isset($databases['pgsql']),
245
			'hasOracle' => isset($databases['oci']),
246
			'databases' => $databases,
247
			'directory' => $dataDir,
248
			'htaccessWorking' => $htAccessWorking,
249
			'errors' => $errors,
250
		);
251
	}
252
253
	/**
254
	 * @param $options
255
	 * @return array
256
	 */
257
	public function install($options) {
258
		$l = $this->l10n;
259
260
		$error = array();
261
		$dbType = $options['dbtype'];
262
263
		if(empty($options['adminlogin'])) {
264
			$error[] = $l->t('Set an admin username.');
265
		}
266
		if(empty($options['adminpass'])) {
267
			$error[] = $l->t('Set an admin password.');
268
		}
269
		if(empty($options['directory'])) {
270
			$options['directory'] = \OC::$SERVERROOT."/data";
271
		}
272
273
		if (!isset(self::$dbSetupClasses[$dbType])) {
274
			$dbType = 'sqlite';
275
		}
276
277
		$username = htmlspecialchars_decode($options['adminlogin']);
278
		$password = htmlspecialchars_decode($options['adminpass']);
279
		$dataDir = htmlspecialchars_decode($options['directory']);
280
281
		$class = self::$dbSetupClasses[$dbType];
282
		/** @var \OC\Setup\AbstractDatabase $dbSetup */
283
		$dbSetup = new $class($l, 'db_structure.xml', $this->config,
284
			$this->logger, $this->random);
285
		$error = array_merge($error, $dbSetup->validate($options));
286
287
		// validate the data directory
288
		if (
289
			(!is_dir($dataDir) and !mkdir($dataDir)) or
290
			!is_writable($dataDir)
291
		) {
292
			$error[] = $l->t("Can't create or write into the data directory %s", array($dataDir));
293
		}
294
295
		if(count($error) != 0) {
296
			return $error;
297
		}
298
299
		$request = \OC::$server->getRequest();
300
301
		//no errors, good
302
		if(isset($options['trusted_domains'])
303
		    && is_array($options['trusted_domains'])) {
304
			$trustedDomains = $options['trusted_domains'];
305
		} else {
306
			$trustedDomains = [$request->getInsecureServerHost()];
307
		}
308
309
		//use sqlite3 when available, otherwise sqlite2 will be used.
310
		if($dbType=='sqlite' and class_exists('SQLite3')) {
311
			$dbType='sqlite3';
312
		}
313
314
		//generate a random salt that is used to salt the local user passwords
315
		$salt = $this->random->generate(30);
316
		// generate a secret
317
		$secret = $this->random->generate(48);
318
319
		//write the config file
320
		$this->config->setSystemValues([
321
			'passwordsalt'		=> $salt,
322
			'secret'			=> $secret,
323
			'trusted_domains'	=> $trustedDomains,
324
			'datadirectory'		=> $dataDir,
325
			'overwrite.cli.url'	=> $request->getServerProtocol() . '://' . $request->getInsecureServerHost() . \OC::$WEBROOT,
326
			'dbtype'			=> $dbType,
327
			'version'			=> implode('.', \OCP\Util::getVersion()),
328
		]);
329
330
		try {
331
			$dbSetup->initialize($options);
332
			$dbSetup->setupDatabase($username);
333
		} catch (\OC\DatabaseSetupException $e) {
334
			$error[] = array(
335
				'error' => $e->getMessage(),
336
				'hint' => $e->getHint()
337
			);
338
			return($error);
339
		} catch (Exception $e) {
340
			$error[] = array(
341
				'error' => 'Error while trying to create admin user: ' . $e->getMessage(),
342
				'hint' => ''
343
			);
344
			return($error);
345
		}
346
347
		//create the user and group
348
		$user =  null;
349
		try {
350
			$user = \OC::$server->getUserManager()->createUser($username, $password);
351
			if (!$user) {
352
				$error[] = "User <$username> could not be created.";
353
			}
354
		} catch(Exception $exception) {
355
			$error[] = $exception->getMessage();
356
		}
357
358
		if(count($error) == 0) {
359
			$config = \OC::$server->getConfig();
360
			$config->setAppValue('core', 'installedat', microtime(true));
361
			$config->setAppValue('core', 'lastupdatedat', microtime(true));
362
			$config->setAppValue('core', 'vendor', $this->getVendor());
363
364
			$group =\OC::$server->getGroupManager()->createGroup('admin');
365
			$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 350 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...
366
367
			// Create a session token for the newly created user
368
			// The token provider requires a working db, so it's not injected on setup
369
			/* @var $userSession User\Session */
370
			$userSession = \OC::$server->getUserSession();
371
			$defaultTokenProvider = \OC::$server->query('OC\Authentication\Token\DefaultTokenProvider');
372
			$userSession->setTokenProvider($defaultTokenProvider);
373
			$userSession->login($username, $password);
374
			$userSession->createSessionToken($request, $userSession->getUser()->getUID(), $username, $password);
375
376
			//guess what this does
377
			Installer::installShippedApps();
378
379
			// create empty file in data dir, so we can later find
380
			// out that this is indeed an ownCloud data directory
381
			file_put_contents($config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').'/.ocdata', '');
382
383
			// Update .htaccess files
384
			Setup::updateHtaccess();
385
			Setup::protectDataDirectory();
386
387
			//try to write logtimezone
388
			if (date_default_timezone_get()) {
389
				$config->setSystemValue('logtimezone', date_default_timezone_get());
390
			}
391
392
			self::installBackgroundJobs();
393
394
			//and we are done
395
			$config->setSystemValue('installed', true);
396
		}
397
398
		return $error;
399
	}
400
401
	public static function installBackgroundJobs() {
402
		\OC::$server->getJobList()->add('\OC\Authentication\Token\DefaultTokenCleanupJob');
403
	}
404
405
	/**
406
	 * @return string Absolute path to htaccess
407
	 */
408
	private function pathToHtaccess() {
409
		return \OC::$SERVERROOT.'/.htaccess';
410
	}
411
412
	/**
413
	 * Append the correct ErrorDocument path for Apache hosts
414
	 */
415
	public static function updateHtaccess() {
416
		$config = \OC::$server->getConfig();
417
418
		// For CLI read the value from overwrite.cli.url
419
		if(\OC::$CLI) {
420
			$webRoot = $config->getSystemValue('overwrite.cli.url', '');
421
			if($webRoot === '') {
422
				return;
423
			}
424
			$webRoot = parse_url($webRoot, PHP_URL_PATH);
425
			$webRoot = rtrim($webRoot, '/');
426
		} else {
427
			$webRoot = !empty(\OC::$WEBROOT) ? \OC::$WEBROOT : '/';
428
		}
429
430
		$setupHelper = new \OC\Setup($config, \OC::$server->getIniWrapper(),
431
			\OC::$server->getL10N('lib'), \OC::$server->getThemingDefaults(), \OC::$server->getLogger(),
432
			\OC::$server->getSecureRandom());
433
434
		$htaccessContent = file_get_contents($setupHelper->pathToHtaccess());
435
		$content = "#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####\n";
436
		$htaccessContent = explode($content, $htaccessContent, 2)[0];
437
438
		//custom 403 error page
439
		$content.= "\nErrorDocument 403 ".$webRoot."/core/templates/403.php";
440
441
		//custom 404 error page
442
		$content.= "\nErrorDocument 404 ".$webRoot."/core/templates/404.php";
443
444
		// Add rewrite rules if the RewriteBase is configured
445
		$rewriteBase = $config->getSystemValue('htaccess.RewriteBase', '');
446
		if($rewriteBase !== '') {
447
			$content .= "\n<IfModule mod_rewrite.c>";
448
			$content .= "\n  Options -MultiViews";
449
			$content .= "\n  RewriteRule ^core/js/oc.js$ index.php [PT,E=PATH_INFO:$1]";
450
			$content .= "\n  RewriteRule ^core/preview.png$ index.php [PT,E=PATH_INFO:$1]";
451
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !\\.(css|js|svg|gif|png|html|ttf|woff|ico|jpg|jpeg)$";
452
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !core/img/favicon.ico$";
453
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/remote.php";
454
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/public.php";
455
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/cron.php";
456
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/core/ajax/update.php";
457
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/status.php";
458
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/ocs/v1.php";
459
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/ocs/v2.php";
460
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/updater/";
461
			$content .= "\n  RewriteCond %{REQUEST_FILENAME} !/ocs-provider/";
462
			$content .= "\n  RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/.*";
463
			$content .= "\n  RewriteRule . index.php [PT,E=PATH_INFO:$1]";
464
			$content .= "\n  RewriteBase " . $rewriteBase;
465
			$content .= "\n  <IfModule mod_env.c>";
466
			$content .= "\n    SetEnv front_controller_active true";
467
			$content .= "\n    <IfModule mod_dir.c>";
468
			$content .= "\n      DirectorySlash off";
469
			$content .= "\n    </IfModule>";
470
			$content .= "\n  </IfModule>";
471
			$content .= "\n</IfModule>";
472
		}
473
474
		if ($content !== '') {
475
			//suppress errors in case we don't have permissions for it
476
			@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...
477
		}
478
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