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