Completed
Push — master ( 7bc3c2...640db3 )
by Morris
113:14 queued 94:18
created

Setup::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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