diff --git a/config/config.sample.php b/config/config.sample.php index 268b7cc9d0..09b59fcb5b 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -807,6 +807,23 @@ $CONFIG = array( ), ), +/** + * Database types that are supported for installation + * Available: + * - sqlite (SQLite3) + * - mysql (MySQL) + * - pgsql (PostgreSQL) + * - oci (Oracle) + * - mssql (Microsoft SQL Server) + */ +'supportedDatabases' => array( + 'sqlite', + 'mysql', + 'pgsql', + 'oci', + 'mssql' +), + /** * Custom CSP policy, changing this will overwrite the standard policy */ diff --git a/core/setup/controller.php b/core/setup/controller.php index 53f247e976..f5f05348fd 100644 --- a/core/setup/controller.php +++ b/core/setup/controller.php @@ -1,6 +1,7 @@ + * Copyright (c) 2014 Lukas Reschke * This file is licensed under the Affero General Public License version 3 or * later. * See the COPYING-README file. @@ -8,7 +9,19 @@ namespace OC\Core\Setup; +use OCP\IConfig; + class Controller { + /** @var \OCP\IConfig */ + protected $config; + + /** + * @param IConfig $config + */ + function __construct(IConfig $config) { + $this->config = $config; + } + public function run($post) { // Check for autosetup: $post = $this->loadAutoConfig($post); @@ -87,28 +100,10 @@ class Controller { * in case of errors/warnings */ public function getSystemInfo() { - $hasSQLite = class_exists('SQLite3'); - $hasMySQL = is_callable('mysql_connect'); - $hasPostgreSQL = is_callable('pg_connect'); - $hasOracle = is_callable('oci_connect'); - $hasMSSQL = is_callable('sqlsrv_connect'); - $databases = array(); - if ($hasSQLite) { - $databases['sqlite'] = 'SQLite'; - } - if ($hasMySQL) { - $databases['mysql'] = 'MySQL/MariaDB'; - } - if ($hasPostgreSQL) { - $databases['pgsql'] = 'PostgreSQL'; - } - if ($hasOracle) { - $databases['oci'] = 'Oracle'; - } - if ($hasMSSQL) { - $databases['mssql'] = 'MS SQL'; - } - $datadir = \OC_Config::getValue('datadirectory', \OC::$SERVERROOT.'/data'); + $setup = new \OC_Setup($this->config); + $databases = $setup->getSupportedDatabases(); + + $dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data'); $vulnerableToNullByte = false; if(@file_exists(__FILE__."\0Nullbyte")) { // Check if the used PHP version is vulnerable to the NULL Byte attack (CVE-2006-7243) $vulnerableToNullByte = true; @@ -119,25 +114,25 @@ class Controller { // Create data directory to test whether the .htaccess works // Notice that this is not necessarily the same data directory as the one // that will effectively be used. - @mkdir($datadir); - if (is_dir($datadir) && is_writable($datadir)) { + @mkdir($dataDir); + $htAccessWorking = true; + if (is_dir($dataDir) && is_writable($dataDir)) { // Protect data directory here, so we can test if the protection is working \OC_Setup::protectDataDirectory(); try { - $htaccessWorking = \OC_Util::isHtaccessWorking(); + $htAccessWorking = \OC_Util::isHtaccessWorking(); } catch (\OC\HintException $e) { $errors[] = array( 'error' => $e->getMessage(), 'hint' => $e->getHint() ); - $htaccessWorking = false; + $htAccessWorking = false; } } if (\OC_Util::runningOnMac()) { $l10n = \OC::$server->getL10N('core'); - $themeName = \OC_Util::getTheme(); $theme = new \OC_Defaults(); $errors[] = array( 'error' => $l10n->t( @@ -150,14 +145,14 @@ class Controller { } return array( - 'hasSQLite' => $hasSQLite, - 'hasMySQL' => $hasMySQL, - 'hasPostgreSQL' => $hasPostgreSQL, - 'hasOracle' => $hasOracle, - 'hasMSSQL' => $hasMSSQL, + 'hasSQLite' => isset($databases['sqlite']), + 'hasMySQL' => isset($databases['mysql']), + 'hasPostgreSQL' => isset($databases['postgre']), + 'hasOracle' => isset($databases['oci']), + 'hasMSSQL' => isset($databases['mssql']), 'databases' => $databases, - 'directory' => $datadir, - 'htaccessWorking' => $htaccessWorking, + 'directory' => $dataDir, + 'htaccessWorking' => $htAccessWorking, 'vulnerableToNullByte' => $vulnerableToNullByte, 'errors' => $errors, ); diff --git a/lib/base.php b/lib/base.php index 4af5b51500..4a5f4e77a5 100644 --- a/lib/base.php +++ b/lib/base.php @@ -717,7 +717,7 @@ class OC { // Check if ownCloud is installed or in maintenance (update) mode if (!OC_Config::getValue('installed', false)) { - $controller = new OC\Core\Setup\Controller(); + $controller = new OC\Core\Setup\Controller(\OC::$server->getConfig()); $controller->run($_POST); exit(); } diff --git a/lib/private/setup.php b/lib/private/setup.php index 75dc1987ee..b82e0be72e 100644 --- a/lib/private/setup.php +++ b/lib/private/setup.php @@ -1,9 +1,27 @@ + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +use OCP\IConfig; class DatabaseSetupException extends \OC\HintException { } class OC_Setup { + /** @var IConfig */ + protected $config; + + /** + * @param IConfig $config + */ + function __construct(IConfig $config) { + $this->config = $config; + } + static $dbSetupClasses = array( 'mysql' => '\OC\Setup\MySQL', 'pgsql' => '\OC\Setup\PostgreSQL', @@ -13,15 +31,99 @@ class OC_Setup { 'sqlite3' => '\OC\Setup\Sqlite', ); + /** + * @return OC_L10N + */ public static function getTrans(){ return \OC::$server->getL10N('lib'); } + /** + * Wrapper around the "class_exists" PHP function to be able to mock it + * @param string $name + * @return bool + */ + public function class_exists($name) { + return class_exists($name); + } + + /** + * Wrapper around the "is_callable" PHP function to be able to mock it + * @param string $name + * @return bool + */ + public function is_callable($name) { + return is_callable($name); + } + + /** + * Get the available and supported databases of this instance + * + * @throws Exception + * @return array + */ + public function getSupportedDatabases() { + $availableDatabases = array( + 'sqlite' => array( + 'type' => 'class', + 'call' => 'SQLite3', + 'name' => 'SQLite' + ), + 'mysql' => array( + 'type' => 'function', + 'call' => 'mysql_connect', + 'name' => 'MySQL/MariaDB' + ), + 'pgsql' => array( + 'type' => 'function', + 'call' => 'oci_connect', + 'name' => 'PostgreSQL' + ), + 'oci' => array( + 'type' => 'function', + 'call' => 'oci_connect', + 'name' => 'Oracle' + ), + 'mssql' => array( + 'type' => 'function', + 'call' => 'sqlsrv_connect', + 'name' => 'MS SQL' + ) + ); + $configuredDatabases = $this->config->getSystemValue('supportedDatabases', + array('sqlite', 'mysql', 'pgsql', 'oci', 'mssql')); + if(!is_array($configuredDatabases)) { + throw new Exception('Supported databases are not properly configured.'); + } + + $supportedDatabases = array(); + + foreach($configuredDatabases as $database) { + if(array_key_exists($database, $availableDatabases)) { + $working = false; + if($availableDatabases[$database]['type'] === 'class') { + $working = $this->class_exists($availableDatabases[$database]['call']); + } elseif ($availableDatabases[$database]['type'] === 'function') { + $working = $this->is_callable($availableDatabases[$database]['call']); + } + if($working) { + $supportedDatabases[$database] = $availableDatabases[$database]['name']; + } + } + } + + return $supportedDatabases; + } + + /** + * @param $options + * @return array + */ public static function install($options) { $l = self::getTrans(); $error = array(); - $dbtype = $options['dbtype']; + $dbType = $options['dbtype']; if(empty($options['adminlogin'])) { $error[] = $l->t('Set an admin username.'); @@ -33,25 +135,25 @@ class OC_Setup { $options['directory'] = OC::$SERVERROOT."/data"; } - if (!isset(self::$dbSetupClasses[$dbtype])) { - $dbtype = 'sqlite'; + if (!isset(self::$dbSetupClasses[$dbType])) { + $dbType = 'sqlite'; } $username = htmlspecialchars_decode($options['adminlogin']); $password = htmlspecialchars_decode($options['adminpass']); - $datadir = htmlspecialchars_decode($options['directory']); + $dataDir = htmlspecialchars_decode($options['directory']); - $class = self::$dbSetupClasses[$dbtype]; + $class = self::$dbSetupClasses[$dbType]; /** @var \OC\Setup\AbstractDatabase $dbSetup */ $dbSetup = new $class(self::getTrans(), 'db_structure.xml'); $error = array_merge($error, $dbSetup->validate($options)); // validate the data directory if ( - (!is_dir($datadir) and !mkdir($datadir)) or - !is_writable($datadir) + (!is_dir($dataDir) and !mkdir($dataDir)) or + !is_writable($dataDir) ) { - $error[] = $l->t("Can't create or write into the data directory %s", array($datadir)); + $error[] = $l->t("Can't create or write into the data directory %s", array($dataDir)); } if(count($error) != 0) { @@ -59,7 +161,7 @@ class OC_Setup { } //no errors, good - if( isset($options['trusted_domains']) + if(isset($options['trusted_domains']) && is_array($options['trusted_domains'])) { $trustedDomains = $options['trusted_domains']; } else { @@ -67,12 +169,12 @@ class OC_Setup { } if (OC_Util::runningOnWindows()) { - $datadir = rtrim(realpath($datadir), '\\'); + $dataDir = rtrim(realpath($dataDir), '\\'); } - //use sqlite3 when available, otherise sqlite2 will be used. - if($dbtype=='sqlite' and class_exists('SQLite3')) { - $dbtype='sqlite3'; + //use sqlite3 when available, otherwise sqlite2 will be used. + if($dbType=='sqlite' and class_exists('SQLite3')) { + $dbType='sqlite3'; } //generate a random salt that is used to salt the local user passwords @@ -85,9 +187,9 @@ class OC_Setup { //write the config file \OC::$server->getConfig()->setSystemValue('trusted_domains', $trustedDomains); - \OC::$server->getConfig()->setSystemValue('datadirectory', $datadir); + \OC::$server->getConfig()->setSystemValue('datadirectory', $dataDir); \OC::$server->getConfig()->setSystemValue('overwrite.cli.url', \OC_Request::serverProtocol() . '://' . \OC_Request::serverHost() . OC::$WEBROOT); - \OC::$server->getConfig()->setSystemValue('dbtype', $dbtype); + \OC::$server->getConfig()->setSystemValue('dbtype', $dbType); \OC::$server->getConfig()->setSystemValue('version', implode('.', OC_Util::getVersion())); try { @@ -110,8 +212,7 @@ class OC_Setup { //create the user and group try { OC_User::createUser($username, $password); - } - catch(Exception $exception) { + } catch(Exception $exception) { $error[] = $exception->getMessage(); } diff --git a/lib/private/util.php b/lib/private/util.php index d600f8a5e6..6cd982c222 100644 --- a/lib/private/util.php +++ b/lib/private/util.php @@ -436,12 +436,9 @@ class OC_Util { } $webServerRestart = false; - //check for database drivers - if (!(is_callable('sqlite_open') or class_exists('SQLite3')) - and !is_callable('mysql_connect') - and !is_callable('pg_connect') - and !is_callable('oci_connect') - ) { + $setup = new OC_Setup($config); + $availableDatabases = $setup->getSupportedDatabases(); + if (empty($availableDatabases)) { $errors[] = array( 'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'), 'hint' => '' //TODO: sane hint diff --git a/tests/lib/setup.php b/tests/lib/setup.php new file mode 100644 index 0000000000..2c1569dd80 --- /dev/null +++ b/tests/lib/setup.php @@ -0,0 +1,103 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +use OCP\IConfig; + +class Test_OC_Setup extends PHPUnit_Framework_TestCase { + + /** @var IConfig */ + protected $config; + /** @var \OC_Setup */ + protected $setupClass; + + public function setUp() { + $this->config = $this->getMock('\OCP\IConfig'); + $this->setupClass = $this->getMock('\OC_Setup', array('class_exists', 'is_callable'), array($this->config)); + } + + public function testGetSupportedDatabasesWithOneWorking() { + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->will($this->returnValue( + array('sqlite', 'mysql', 'oci') + )); + $this->setupClass + ->expects($this->once()) + ->method('class_exists') + ->will($this->returnValue(true)); + $this->setupClass + ->expects($this->exactly(2)) + ->method('is_callable') + ->will($this->returnValue(false)); + $result = $this->setupClass->getSupportedDatabases(); + $expectedResult = array( + 'sqlite' => 'SQLite' + ); + + $this->assertSame($expectedResult, $result); + } + + public function testGetSupportedDatabasesWithNoWorking() { + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->will($this->returnValue( + array('sqlite', 'mysql', 'oci', 'pgsql') + )); + $this->setupClass + ->expects($this->once()) + ->method('class_exists') + ->will($this->returnValue(false)); + $this->setupClass + ->expects($this->exactly(3)) + ->method('is_callable') + ->will($this->returnValue(false)); + $result = $this->setupClass->getSupportedDatabases(); + + $this->assertSame(array(), $result); + } + + public function testGetSupportedDatabasesWitAllWorking() { + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->will($this->returnValue( + array('sqlite', 'mysql', 'pgsql', 'oci', 'mssql') + )); + $this->setupClass + ->expects($this->once()) + ->method('class_exists') + ->will($this->returnValue(true)); + $this->setupClass + ->expects($this->exactly(4)) + ->method('is_callable') + ->will($this->returnValue(true)); + $result = $this->setupClass->getSupportedDatabases(); + $expectedResult = array( + 'sqlite' => 'SQLite', + 'mysql' => 'MySQL/MariaDB', + 'pgsql' => 'PostgreSQL', + 'oci' => 'Oracle', + 'mssql' => 'MS SQL' + ); + $this->assertSame($expectedResult, $result); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Supported databases are not properly configured. + */ + public function testGetSupportedDatabaseException() { + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->will($this->returnValue('NotAnArray')); + $this->setupClass->getSupportedDatabases(); + } +} \ No newline at end of file