diff options
-rw-r--r-- | apps/files/ajax/download.php | 11 | ||||
-rw-r--r-- | apps/files/css/files.css | 25 | ||||
-rw-r--r-- | apps/files/js/fileactions.js | 35 | ||||
-rw-r--r-- | apps/files/js/filelist.js | 16 | ||||
-rw-r--r-- | apps/files/js/files.js | 27 | ||||
-rw-r--r-- | apps/files/tests/js/fileactionsSpec.js | 4 | ||||
-rw-r--r-- | apps/files/tests/js/filelistSpec.js | 10 | ||||
-rw-r--r-- | core/command/maintenance/install.php | 5 | ||||
-rw-r--r-- | core/js/js.js | 32 | ||||
-rw-r--r-- | lib/base.php | 4 | ||||
-rw-r--r-- | lib/private/files/view.php | 4 | ||||
-rw-r--r-- | lib/private/setup.php | 56 | ||||
-rw-r--r-- | lib/private/setup/abstractdatabase.php | 47 | ||||
-rw-r--r-- | lib/private/setup/mysql.php | 187 | ||||
-rw-r--r-- | lib/private/setup/oci.php | 38 | ||||
-rw-r--r-- | lib/private/setup/postgresql.php | 40 | ||||
-rw-r--r-- | lib/private/util.php | 3 | ||||
-rw-r--r-- | settings/admin.php | 2 | ||||
-rw-r--r-- | settings/templates/admin.php | 25 | ||||
-rw-r--r-- | tests/lib/appframework/utility/SimpleContainerTest.php | 6 | ||||
-rw-r--r-- | tests/lib/files/pathverificationtest.php | 4 | ||||
-rw-r--r-- | tests/lib/setup.php | 28 |
22 files changed, 422 insertions, 187 deletions
diff --git a/apps/files/ajax/download.php b/apps/files/ajax/download.php index e67635ab853..26bab8837b4 100644 --- a/apps/files/ajax/download.php +++ b/apps/files/ajax/download.php @@ -39,4 +39,15 @@ if (!is_array($files_list)) { $files_list = array($files); } +/** + * this sets a cookie to be able to recognize the start of the download + * the content must not be longer than 32 characters and must only contain + * alphanumeric characters + */ +if(isset($_GET['downloadStartSecret']) + && !isset($_GET['downloadStartSecret'][32]) + && preg_match('!^[a-zA-Z0-9]+$!', $_GET['downloadStartSecret']) === 1) { + setcookie('ocDownloadStarted', $_GET['downloadStartSecret'], time() + 20, '/'); +} + OC_Files::get($dir, $files_list, $_SERVER['REQUEST_METHOD'] == 'HEAD'); diff --git a/apps/files/css/files.css b/apps/files/css/files.css index e4bf791761d..f2f2c5ac3bc 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -583,14 +583,23 @@ a.action>img { #fileList tr:focus a.action, #fileList a.action.permanent, #fileList tr:hover a.action.no-permission:hover, -#fileList tr:focus a.action.no-permission:focus -/*#fileList .name:focus .action*/ { +#fileList tr:focus a.action.no-permission:focus, +/*#fileList .name:focus .action,*/ +/* also enforce the low opacity for disabled links that are hovered/focused */ +.ie8 #fileList a.action.disabled:hover img, +#fileList tr:hover a.action.disabled:hover, +#fileList tr:focus a.action.disabled:focus, +#fileList .name:focus a.action.disabled:focus, +#fileList a.action.disabled img { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; filter: alpha(opacity=50); opacity: .5; display:inline; } .ie8 #fileList a.action:hover img, +#fileList tr a.action.disabled.action-download, +#fileList tr:hover a.action.disabled.action-download:hover, +#fileList tr:focus a.action.disabled.action-download:focus, #fileList tr:hover a.action:hover, #fileList tr:focus a.action:focus, #fileList .name:focus a.action:focus { @@ -599,6 +608,18 @@ a.action>img { opacity: 1; display:inline; } +#fileList tr a.action.disabled { + background: none; +} + +#selectedActionsList a.download.disabled, +#fileList tr a.action.action-download.disabled { + color: #000000; +} + +#fileList tr:hover a.action.disabled:hover * { + cursor: default; +} .summary { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index 1956fda0077..8dd26d71c3e 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -478,8 +478,21 @@ }, function (filename, context) { var dir = context.dir || context.fileList.getCurrentDirectory(); var url = context.fileList.getDownloadUrl(filename, dir); + + var downloadFileaction = $(context.$file).find('.fileactions .action-download'); + + // don't allow a second click on the download action + if(downloadFileaction.hasClass('disabled')) { + return; + } + if (url) { - OC.redirect(url); + var disableLoadingState = function(){ + OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, false); + }; + + OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, true); + OCA.Files.Files.handleDownload(url, disableLoadingState); } }, t('files', 'Download')); } @@ -488,6 +501,26 @@ OCA.Files.FileActions = FileActions; /** + * Replaces the download icon with a loading spinner and vice versa + * - also adds the class disabled to the passed in element + * + * @param downloadButtonElement download fileaction + * @param {boolean} showIt whether to show the spinner(true) or to hide it(false) + */ + OCA.Files.FileActions.updateFileActionSpinner = function(downloadButtonElement, showIt) { + var icon = downloadButtonElement.find('img'), + sourceImage = icon.attr('src'); + + if(showIt) { + downloadButtonElement.addClass('disabled'); + icon.attr('src', sourceImage.replace('actions/download.svg', 'loading-small.gif')); + } else { + downloadButtonElement.removeClass('disabled'); + icon.attr('src', sourceImage.replace('loading-small.gif', 'actions/download.svg')); + } + }; + + /** * File action attributes. * * @todo make this a real class in the future diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 8236ef3b4ac..a7d4e41d0e0 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -417,7 +417,21 @@ else { files = _.pluck(this.getSelectedFiles(), 'name'); } - OC.redirect(this.getDownloadUrl(files, dir)); + + var downloadFileaction = $('#selectedActionsList').find('.download'); + + // don't allow a second click on the download action + if(downloadFileaction.hasClass('disabled')) { + event.preventDefault(); + return; + } + + var disableLoadingState = function(){ + OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, false); + }; + + OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, true); + OCA.Files.Files.handleDownload(this.getDownloadUrl(files, dir), disableLoadingState); return false; }, diff --git a/apps/files/js/files.js b/apps/files/js/files.js index 034045ee40b..19cc3b26e44 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -271,8 +271,33 @@ FileList.scrollTo(getURLParameter('scrollto')); } */ + }, + + /** + * Handles the download and calls the callback function once the download has started + * - browser sends download request and adds parameter with a token + * - server notices this token and adds a set cookie to the download response + * - browser now adds this cookie for the domain + * - JS periodically checks for this cookie and then knows when the download has started to call the callback + * + * @param {string} url download URL + * @param {function} callback function to call once the download has started + */ + handleDownload: function(url, callback) { + var randomToken = Math.random().toString(36).substring(2), + checkForDownloadCookie = function() { + if (!OC.Util.isCookieSetToValue('ocDownloadStarted', randomToken)){ + return false; + } else { + callback(); + return true; + } + }; + + OC.redirect(url + '&downloadStartSecret=' + randomToken); + OC.Util.waitFor(checkForDownloadCookie, 500); } - } + }; Files._updateStorageStatisticsDebounced = _.debounce(Files._updateStorageStatistics, 250); OCA.Files.Files = Files; diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js index 53fa8707674..e420ab828af 100644 --- a/apps/files/tests/js/fileactionsSpec.js +++ b/apps/files/tests/js/fileactionsSpec.js @@ -105,7 +105,7 @@ describe('OCA.Files.FileActions tests', function() { $tr.find('.action-download').click(); expect(redirectStub.calledOnce).toEqual(true); - expect(redirectStub.getCall(0).args[0]).toEqual( + expect(redirectStub.getCall(0).args[0]).toContain( OC.webroot + '/index.php/apps/files/ajax/download.php' + '?dir=%2Fsubdir&files=testName.txt'); @@ -129,7 +129,7 @@ describe('OCA.Files.FileActions tests', function() { $tr.find('.action-download').click(); expect(redirectStub.calledOnce).toEqual(true); - expect(redirectStub.getCall(0).args[0]).toEqual( + expect(redirectStub.getCall(0).args[0]).toContain( OC.webroot + '/index.php/apps/files/ajax/download.php' + '?dir=%2Fanotherpath%2Fthere&files=testName.txt' ); diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index 316df0281e9..09d698088ae 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -77,8 +77,8 @@ describe('OCA.Files.FileList tests', function() { '<th id="headerName" class="hidden column-name">' + '<input type="checkbox" id="select_all_files" class="select-all">' + '<a class="name columntitle" data-sort="name"><span>Name</span><span class="sort-indicator"></span></a>' + - '<span class="selectedActions hidden">' + - '<a href class="download">Download</a>' + + '<span id="selectedActionsList" class="selectedActions hidden">' + + '<a href class="download"><img src="actions/download.svg">Download</a>' + '<a href class="delete-selected">Delete</a></span>' + '</th>' + '<th class="hidden column-size"><a class="columntitle" data-sort="size"><span class="sort-indicator"></span></a></th>' + @@ -1775,7 +1775,7 @@ describe('OCA.Files.FileList tests', function() { var redirectStub = sinon.stub(OC, 'redirect'); $('.selectedActions .download').click(); expect(redirectStub.calledOnce).toEqual(true); - expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=%5B%22One.txt%22%2C%22Three.pdf%22%2C%22somedir%22%5D'); + expect(redirectStub.getCall(0).args[0]).toContain(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=%5B%22One.txt%22%2C%22Three.pdf%22%2C%22somedir%22%5D'); redirectStub.restore(); }); it('Downloads root folder when all selected in root folder', function() { @@ -1784,7 +1784,7 @@ describe('OCA.Files.FileList tests', function() { var redirectStub = sinon.stub(OC, 'redirect'); $('.selectedActions .download').click(); expect(redirectStub.calledOnce).toEqual(true); - expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files='); + expect(redirectStub.getCall(0).args[0]).toContain(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files='); redirectStub.restore(); }); it('Downloads parent folder when all selected in subfolder', function() { @@ -1792,7 +1792,7 @@ describe('OCA.Files.FileList tests', function() { var redirectStub = sinon.stub(OC, 'redirect'); $('.selectedActions .download').click(); expect(redirectStub.calledOnce).toEqual(true); - expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=subdir'); + expect(redirectStub.getCall(0).args[0]).toContain(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=subdir'); redirectStub.restore(); }); }); diff --git a/core/command/maintenance/install.php b/core/command/maintenance/install.php index 2fea5add438..7f5d9cae647 100644 --- a/core/command/maintenance/install.php +++ b/core/command/maintenance/install.php @@ -61,7 +61,10 @@ class Install extends Command { protected function execute(InputInterface $input, OutputInterface $output) { // validate the environment - $setupHelper = new Setup($this->config, \OC::$server->getIniWrapper(), \OC::$server->getL10N('lib'), new \OC_Defaults()); + $server = \OC::$server; + $setupHelper = new Setup($this->config, $server->getIniWrapper(), + $server->getL10N('lib'), new \OC_Defaults(), $server->getLogger(), + $server->getSecureRandom()); $sysInfo = $setupHelper->getSystemInfo(true); $errors = $sysInfo['errors']; if (count($errors) > 0) { diff --git a/core/js/js.js b/core/js/js.js index 8380d56e31e..45c9c90362f 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -1609,8 +1609,38 @@ OC.Util = { } } return aa.length - bb.length; + }, + /** + * Calls the callback in a given interval until it returns true + * @param {function} callback + * @param {integer} interval in milliseconds + */ + waitFor: function(callback, interval) { + var internalCallback = function() { + if(callback() !== true) { + setTimeout(internalCallback, interval); + } + }; + + internalCallback(); + }, + /** + * Checks if a cookie with the given name is present and is set to the provided value. + * @param {string} name name of the cookie + * @param {string} value value of the cookie + * @return {boolean} true if the cookie with the given name has the given value + */ + isCookieSetToValue: function(name, value) { + var cookies = document.cookie.split(';'); + for (var i=0; i < cookies.length; i++) { + var cookie = cookies[i].split('='); + if (cookie[0].trim() === name && cookie[1].trim() === value) { + return true; + } + } + return false; } -} +}; /** * Utility class for the history API, diff --git a/lib/base.php b/lib/base.php index fde67839560..299970e269c 100644 --- a/lib/base.php +++ b/lib/base.php @@ -835,7 +835,9 @@ class OC { // Check if ownCloud is installed or in maintenance (update) mode if (!$systemConfig->getValue('installed', false)) { \OC::$server->getSession()->clear(); - $setupHelper = new OC\Setup(\OC::$server->getConfig(), \OC::$server->getIniWrapper(), \OC::$server->getL10N('lib'), new \OC_Defaults()); + $setupHelper = new OC\Setup(\OC::$server->getConfig(), \OC::$server->getIniWrapper(), + \OC::$server->getL10N('lib'), new \OC_Defaults(), \OC::$server->getLogger(), + \OC::$server->getSecureRandom()); $controller = new OC\Core\Setup\Controller($setupHelper); $controller->run($_POST); exit(); diff --git a/lib/private/files/view.php b/lib/private/files/view.php index cb3c05d2bca..1a6be73d5bb 100644 --- a/lib/private/files/view.php +++ b/lib/private/files/view.php @@ -1656,11 +1656,11 @@ class View { } // verify database - e.g. mysql only 3-byte chars - if (preg_match('%^(?: + if (preg_match('%(?: \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 -)*$%xs', $fileName)) { +)%xs', $fileName)) { throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names')); } diff --git a/lib/private/setup.php b/lib/private/setup.php index 870480feaa0..8f1ae389e45 100644 --- a/lib/private/setup.php +++ b/lib/private/setup.php @@ -39,6 +39,8 @@ use bantu\IniGetWrapper\IniGetWrapper; use Exception; use OCP\IConfig; use OCP\IL10N; +use OCP\ILogger; +use OCP\Security\ISecureRandom; class Setup { /** @var \OCP\IConfig */ @@ -49,6 +51,10 @@ class Setup { protected $l10n; /** @var \OC_Defaults */ protected $defaults; + /** @var ILogger */ + protected $logger; + /** @var ISecureRandom */ + protected $random; /** * @param IConfig $config @@ -58,11 +64,16 @@ class Setup { function __construct(IConfig $config, IniGetWrapper $iniWrapper, IL10N $l10n, - \OC_Defaults $defaults) { + \OC_Defaults $defaults, + ILogger $logger, + ISecureRandom $random + ) { $this->config = $config; $this->iniWrapper = $iniWrapper; $this->l10n = $l10n; $this->defaults = $defaults; + $this->logger = $logger; + $this->random = $random; } static $dbSetupClasses = array( @@ -78,7 +89,7 @@ class Setup { * @param string $name * @return bool */ - public function class_exists($name) { + protected function class_exists($name) { return class_exists($name); } @@ -87,11 +98,20 @@ class Setup { * @param string $name * @return bool */ - public function is_callable($name) { + protected function is_callable($name) { return is_callable($name); } /** + * Wrapper around \PDO::getAvailableDrivers + * + * @return array + */ + protected function getAvailableDbDriversForPdo() { + return \PDO::getAvailableDrivers(); + } + + /** * Get the available and supported databases of this instance * * @param bool $allowAllDatabases @@ -106,8 +126,8 @@ class Setup { 'name' => 'SQLite' ), 'mysql' => array( - 'type' => 'function', - 'call' => 'mysql_connect', + 'type' => 'pdo', + 'call' => 'mysql', 'name' => 'MySQL/MariaDB' ), 'pgsql' => array( @@ -136,10 +156,15 @@ class Setup { 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']); + $type = $availableDatabases[$database]['type']; + $call = $availableDatabases[$database]['call']; + + if($type === 'class') { + $working = $this->class_exists($call); + } elseif ($type === 'function') { + $working = $this->is_callable($call); + } elseif($type === 'pdo') { + $working = in_array($call, $this->getAvailableDbDriversForPdo(), TRUE); } if($working) { $supportedDatabases[$database] = $availableDatabases[$database]['name']; @@ -249,7 +274,8 @@ class Setup { $class = self::$dbSetupClasses[$dbType]; /** @var \OC\Setup\AbstractDatabase $dbSetup */ - $dbSetup = new $class($l, 'db_structure.xml'); + $dbSetup = new $class($l, 'db_structure.xml', $this->config, + $this->logger, $this->random); $error = array_merge($error, $dbSetup->validate($options)); // validate the data directory @@ -284,9 +310,9 @@ class Setup { } //generate a random salt that is used to salt the local user passwords - $salt = \OC::$server->getSecureRandom()->getLowStrengthGenerator()->generate(30); + $salt = $this->random->getLowStrengthGenerator()->generate(30); // generate a secret - $secret = \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(48); + $secret = $this->random->getMediumStrengthGenerator()->generate(48); //write the config file $this->config->setSystemValues([ @@ -351,7 +377,7 @@ class Setup { //try to write logtimezone if (date_default_timezone_get()) { - \OC_Config::setValue('logtimezone', date_default_timezone_get()); + $config->setSystemValue('logtimezone', date_default_timezone_get()); } //and we are done @@ -389,7 +415,9 @@ class Setup { * @throws \OC\HintException If .htaccess does not include the current version */ public static function updateHtaccess() { - $setupHelper = new \OC\Setup(\OC::$server->getConfig(), \OC::$server->getIniWrapper(), \OC::$server->getL10N('lib'), new \OC_Defaults()); + $setupHelper = new \OC\Setup(\OC::$server->getConfig(), \OC::$server->getIniWrapper(), + \OC::$server->getL10N('lib'), new \OC_Defaults(), \OC::$server->getLogger(), + \OC::$server->getSecureRandom()); if(!$setupHelper->isCurrentHtaccess()) { throw new \OC\HintException('.htaccess file has the wrong version. Please upload the correct version. Maybe you forgot to replace it after updating?'); } diff --git a/lib/private/setup/abstractdatabase.php b/lib/private/setup/abstractdatabase.php index 13daf1782fc..1ec853c3b02 100644 --- a/lib/private/setup/abstractdatabase.php +++ b/lib/private/setup/abstractdatabase.php @@ -22,22 +22,39 @@ */ namespace OC\Setup; +use OCP\IConfig; +use OCP\ILogger; +use OCP\Security\ISecureRandom; + abstract class AbstractDatabase { - /** - * @var \OC_L10N - */ + /** @var \OC_L10N */ protected $trans; + /** @var string */ protected $dbDefinitionFile; - protected $dbuser; - protected $dbpassword; - protected $dbname; - protected $dbhost; - protected $tableprefix; + /** @var string */ + protected $dbUser; + /** @var string */ + protected $dbPassword; + /** @var string */ + protected $dbName; + /** @var string */ + protected $dbHost; + /** @var string */ + protected $tablePrefix; + /** @var IConfig */ + protected $config; + /** @var ILogger */ + protected $logger; + /** @var ISecureRandom */ + protected $random; - public function __construct($trans, $dbDefinitionFile) { + public function __construct($trans, $dbDefinitionFile, IConfig $config, ILogger $logger, ISecureRandom $random) { $this->trans = $trans; $this->dbDefinitionFile = $dbDefinitionFile; + $this->config = $config; + $this->logger = $logger; + $this->random = $random; } public function validate($config) { @@ -61,17 +78,17 @@ abstract class AbstractDatabase { $dbHost = !empty($config['dbhost']) ? $config['dbhost'] : 'localhost'; $dbTablePrefix = isset($config['dbtableprefix']) ? $config['dbtableprefix'] : 'oc_'; - \OC_Config::setValues([ + $this->config->setSystemValues([ 'dbname' => $dbName, 'dbhost' => $dbHost, 'dbtableprefix' => $dbTablePrefix, ]); - $this->dbuser = $dbUser; - $this->dbpassword = $dbPass; - $this->dbname = $dbName; - $this->dbhost = $dbHost; - $this->tableprefix = $dbTablePrefix; + $this->dbUser = $dbUser; + $this->dbPassword = $dbPass; + $this->dbName = $dbName; + $this->dbHost = $dbHost; + $this->tablePrefix = $dbTablePrefix; } abstract public function setupDatabase($userName); diff --git a/lib/private/setup/mysql.php b/lib/private/setup/mysql.php index 9cf102393b8..5597592f21e 100644 --- a/lib/private/setup/mysql.php +++ b/lib/private/setup/mysql.php @@ -23,110 +23,139 @@ */ namespace OC\Setup; +use OC\DB\ConnectionFactory; +use OCP\IDBConnection; + class MySQL extends AbstractDatabase { public $dbprettyname = 'MySQL/MariaDB'; public function setupDatabase($username) { //check if the database user has admin right - $connection = @mysql_connect($this->dbhost, $this->dbuser, $this->dbpassword); - if(!$connection) { - throw new \OC\DatabaseSetupException($this->trans->t('MySQL/MariaDB username and/or password not valid'), - $this->trans->t('You need to enter either an existing account or the administrator.')); - } - //user already specified in config - $oldUser=\OC_Config::getValue('dbuser', false); - - //we don't have a dbuser specified in config - if($this->dbuser!=$oldUser) { - //add prefix to the admin username to prevent collisions - $adminUser=substr('oc_'.$username, 0, 16); - - $i = 1; - while(true) { - //this should be enough to check for admin rights in mysql - $query="SELECT user FROM mysql.user WHERE user='$adminUser'"; - - $result = mysql_query($query, $connection); + $connection = $this->connect(); - //current dbuser has admin rights - if($result) { - //new dbuser does not exist - if(mysql_num_rows($result) === 0) { - //use the admin login data for the new database user - $this->dbuser=$adminUser; - - //create a random password so we don't need to store the admin password in the config file - $this->dbpassword=\OC_Util::generateRandomBytes(30); - - $this->createDBUser($connection); - - break; - } else { - //repeat with different username - $length=strlen((string)$i); - $adminUser=substr('oc_'.$username, 0, 16 - $length).$i; - $i++; - } - } else { - break; - } - }; - - \OC_Config::setValues([ - 'dbuser' => $this->dbuser, - 'dbpassword' => $this->dbpassword, - ]); - } + $this->createSpecificUser($username, $connection); //create the database $this->createDatabase($connection); //fill the database if needed - $query='select count(*) from information_schema.tables' - ." where table_schema='".$this->dbname."' AND table_name = '".$this->tableprefix."users';"; - $result = mysql_query($query, $connection); - if($result) { - $row=mysql_fetch_row($result); - } + $query='select count(*) from information_schema.tables where table_schema=? AND table_name = ?'; + $result = $connection->executeQuery($query, [$this->dbName, $this->tablePrefix.'users']); + $row = $result->fetch(); if(!$result or $row[0]==0) { \OC_DB::createDbFromStructure($this->dbDefinitionFile); } - mysql_close($connection); } + /** + * @param \OC\DB\Connection $connection + */ private function createDatabase($connection) { - $name = $this->dbname; - $user = $this->dbuser; - //we cant use OC_BD functions here because we need to connect as the administrative user. - $query = "CREATE DATABASE IF NOT EXISTS `$name` CHARACTER SET utf8 COLLATE utf8_bin;"; - $result = mysql_query($query, $connection); - if(!$result) { - $entry = $this->trans->t('DB Error: "%s"', array(mysql_error($connection))) . '<br />'; - $entry .= $this->trans->t('Offending command was: "%s"', array($query)) . '<br />'; - \OCP\Util::writeLog('setup.mysql', $entry, \OCP\Util::WARN); - } - $query="GRANT ALL PRIVILEGES ON `$name` . * TO '$user'"; + try{ + $name = $this->dbName; + $user = $this->dbUser; + //we cant use OC_BD functions here because we need to connect as the administrative user. + $query = "CREATE DATABASE IF NOT EXISTS `$name` CHARACTER SET utf8 COLLATE utf8_bin;"; + $connection->executeUpdate($query); - //this query will fail if there aren't the right permissions, ignore the error - mysql_query($query, $connection); + //this query will fail if there aren't the right permissions, ignore the error + $query="GRANT ALL PRIVILEGES ON `$name` . * TO '$user'"; + $connection->executeUpdate($query); + } catch (\Exception $ex) { + $this->logger->error('Database creation failed: {error}', [ + 'app' => 'mysql.setup', + 'error' => $ex->getMessage() + ]); + } } + /** + * @param IDbConnection $connection + * @throws \OC\DatabaseSetupException + */ private function createDBUser($connection) { - $name = $this->dbuser; - $password = $this->dbpassword; + $name = $this->dbUser; + $password = $this->dbPassword; // we need to create 2 accounts, one for global use and one for local user. if we don't specify the local one, // the anonymous user would take precedence when there is one. $query = "CREATE USER '$name'@'localhost' IDENTIFIED BY '$password'"; - $result = mysql_query($query, $connection); - if (!$result) { - throw new \OC\DatabaseSetupException($this->trans->t("MySQL/MariaDB user '%s'@'localhost' exists already.", array($name)), - $this->trans->t("Drop this user from MySQL/MariaDB", array($name))); - } + $connection->executeUpdate($query); $query = "CREATE USER '$name'@'%' IDENTIFIED BY '$password'"; - $result = mysql_query($query, $connection); - if (!$result) { - throw new \OC\DatabaseSetupException($this->trans->t("MySQL/MariaDB user '%s'@'%%' already exists", array($name)), - $this->trans->t("Drop this user from MySQL/MariaDB.")); + $connection->executeUpdate($query); + } + + /** + * @return \OC\DB\Connection + * @throws \OC\DatabaseSetupException + */ + private function connect() { + $type = 'mysql'; + $connectionParams = array( + 'host' => $this->dbHost, + 'user' => $this->dbUser, + 'password' => $this->dbPassword, + 'tablePrefix' => $this->tablePrefix, + ); + $cf = new ConnectionFactory(); + return $cf->getConnection($type, $connectionParams); + } + + /** + * @param $username + * @param IDBConnection $connection + * @return array + */ + private function createSpecificUser($username, $connection) { + try { + //user already specified in config + $oldUser = $this->config->getSystemValue('dbuser', false); + + //we don't have a dbuser specified in config + if ($this->dbUser !== $oldUser) { + //add prefix to the admin username to prevent collisions + $adminUser = substr('oc_' . $username, 0, 16); + + $i = 1; + while (true) { + //this should be enough to check for admin rights in mysql + $query = 'SELECT user FROM mysql.user WHERE user=?'; + $result = $connection->executeQuery($query, [$adminUser]); + + //current dbuser has admin rights + if ($result) { + $data = $result->fetchAll(); + //new dbuser does not exist + if (count($data) === 0) { + //use the admin login data for the new database user + $this->dbUser = $adminUser; + + //create a random password so we don't need to store the admin password in the config file + $this->dbPassword = $this->random->getMediumStrengthGenerator()->generate(30); + + $this->createDBUser($connection); + + break; + } else { + //repeat with different username + $length = strlen((string)$i); + $adminUser = substr('oc_' . $username, 0, 16 - $length) . $i; + $i++; + } + } else { + break; + } + }; + } + } catch (\Exception $ex) { + $this->logger->error('Specific user creation failed: {error}', [ + 'app' => 'mysql.setup', + 'error' => $ex->getMessage() + ]); } + + $this->config->setSystemValues([ + 'dbuser' => $this->dbUser, + 'dbpassword' => $this->dbPassword, + ]); } } diff --git a/lib/private/setup/oci.php b/lib/private/setup/oci.php index d46d5529da0..1e1eb1ff54e 100644 --- a/lib/private/setup/oci.php +++ b/lib/private/setup/oci.php @@ -38,10 +38,10 @@ class OCI extends AbstractDatabase { $this->dbtablespace = 'USERS'; } // allow empty hostname for oracle - $this->dbhost = $config['dbhost']; + $this->dbHost = $config['dbhost']; \OC_Config::setValues([ - 'dbhost' => $this->dbhost, + 'dbhost' => $this->dbHost, 'dbtablespace' => $this->dbtablespace, ]); } @@ -58,8 +58,8 @@ class OCI extends AbstractDatabase { } public function setupDatabase($username) { - $e_host = addslashes($this->dbhost); - $e_dbname = addslashes($this->dbname); + $e_host = addslashes($this->dbHost); + $e_dbname = addslashes($this->dbName); //check if the database user has admin right if ($e_host == '') { $easy_connect_string = $e_dbname; // use dbname as easy connect name @@ -67,7 +67,7 @@ class OCI extends AbstractDatabase { $easy_connect_string = '//'.$e_host.'/'.$e_dbname; } \OCP\Util::writeLog('setup oracle', 'connect string: ' . $easy_connect_string, \OCP\Util::DEBUG); - $connection = @oci_connect($this->dbuser, $this->dbpassword, $easy_connect_string); + $connection = @oci_connect($this->dbUser, $this->dbPassword, $easy_connect_string); if(!$connection) { $errorMessage = $this->getLastError(); if ($errorMessage) { @@ -103,23 +103,23 @@ class OCI extends AbstractDatabase { //use the admin login data for the new database user //add prefix to the oracle user name to prevent collisions - $this->dbuser='oc_'.$username; + $this->dbUser='oc_'.$username; //create a new password so we don't need to store the admin config in the config file - $this->dbpassword=\OC_Util::generateRandomBytes(30); + $this->dbPassword=\OC_Util::generateRandomBytes(30); //oracle passwords are treated as identifiers: // must start with alphanumeric char // needs to be shortened to 30 bytes, as the two " needed to escape the identifier count towards the identifier length. - $this->dbpassword=substr($this->dbpassword, 0, 30); + $this->dbPassword=substr($this->dbPassword, 0, 30); $this->createDBUser($connection); } } \OC_Config::setValues([ - 'dbuser' => $this->dbuser, - 'dbname' => $this->dbname, - 'dbpassword' => $this->dbpassword, + 'dbuser' => $this->dbUser, + 'dbname' => $this->dbName, + 'dbpassword' => $this->dbPassword, ]); //create the database not necessary, oracle implies user = schema @@ -131,26 +131,26 @@ class OCI extends AbstractDatabase { oci_close($connection); // connect to the oracle database (schema=$this->dbuser) an check if the schema needs to be filled - $this->dbuser = \OC_Config::getValue('dbuser'); + $this->dbUser = \OC_Config::getValue('dbuser'); //$this->dbname = \OC_Config::getValue('dbname'); - $this->dbpassword = \OC_Config::getValue('dbpassword'); + $this->dbPassword = \OC_Config::getValue('dbpassword'); - $e_host = addslashes($this->dbhost); - $e_dbname = addslashes($this->dbname); + $e_host = addslashes($this->dbHost); + $e_dbname = addslashes($this->dbName); if ($e_host == '') { $easy_connect_string = $e_dbname; // use dbname as easy connect name } else { $easy_connect_string = '//'.$e_host.'/'.$e_dbname; } - $connection = @oci_connect($this->dbuser, $this->dbpassword, $easy_connect_string); + $connection = @oci_connect($this->dbUser, $this->dbPassword, $easy_connect_string); if(!$connection) { throw new \OC\DatabaseSetupException($this->trans->t('Oracle username and/or password not valid'), $this->trans->t('You need to enter either an existing account or the administrator.')); } $query = "SELECT count(*) FROM user_tables WHERE table_name = :un"; $stmt = oci_parse($connection, $query); - $un = $this->tableprefix.'users'; + $un = $this->tablePrefix.'users'; oci_bind_by_name($stmt, ':un', $un); if (!$stmt) { $entry = $this->trans->t('DB Error: "%s"', array($this->getLastError($connection))) . '<br />'; @@ -171,8 +171,8 @@ class OCI extends AbstractDatabase { * @param resource $connection */ private function createDBUser($connection) { - $name = $this->dbuser; - $password = $this->dbpassword; + $name = $this->dbUser; + $password = $this->dbPassword; $query = "SELECT * FROM all_users WHERE USERNAME = :un"; $stmt = oci_parse($connection, $query); if (!$stmt) { diff --git a/lib/private/setup/postgresql.php b/lib/private/setup/postgresql.php index c8fd3b98fe4..319b6676ef8 100644 --- a/lib/private/setup/postgresql.php +++ b/lib/private/setup/postgresql.php @@ -27,9 +27,9 @@ class PostgreSQL extends AbstractDatabase { public $dbprettyname = 'PostgreSQL'; public function setupDatabase($username) { - $e_host = addslashes($this->dbhost); - $e_user = addslashes($this->dbuser); - $e_password = addslashes($this->dbpassword); + $e_host = addslashes($this->dbHost); + $e_user = addslashes($this->dbUser); + $e_password = addslashes($this->dbPassword); // Fix database with port connection if(strpos($e_host, ':')) { @@ -43,7 +43,7 @@ class PostgreSQL extends AbstractDatabase { $connection = @pg_connect($connection_string); if(!$connection) { // Try if we can connect to the DB with the specified name - $e_dbname = addslashes($this->dbname); + $e_dbname = addslashes($this->dbName); $connection_string = "host='$e_host' dbname='$e_dbname' user='$e_user' port='$port' password='$e_password'"; $connection = @pg_connect($connection_string); @@ -51,7 +51,7 @@ class PostgreSQL extends AbstractDatabase { throw new \OC\DatabaseSetupException($this->trans->t('PostgreSQL username and/or password not valid'), $this->trans->t('You need to enter either an existing account or the administrator.')); } - $e_user = pg_escape_string($this->dbuser); + $e_user = pg_escape_string($this->dbUser); //check for roles creation rights in postgresql $query="SELECT 1 FROM pg_roles WHERE rolcreaterole=TRUE AND rolname='$e_user'"; $result = pg_query($connection, $query); @@ -59,16 +59,16 @@ class PostgreSQL extends AbstractDatabase { //use the admin login data for the new database user //add prefix to the postgresql user name to prevent collisions - $this->dbuser='oc_'.$username; + $this->dbUser='oc_'.$username; //create a new password so we don't need to store the admin config in the config file - $this->dbpassword=\OC_Util::generateRandomBytes(30); + $this->dbPassword=\OC_Util::generateRandomBytes(30); $this->createDBUser($connection); } \OC_Config::setValues([ - 'dbuser' => $this->dbuser, - 'dbpassword' => $this->dbpassword, + 'dbuser' => $this->dbUser, + 'dbpassword' => $this->dbPassword, ]); //create the database @@ -78,13 +78,13 @@ class PostgreSQL extends AbstractDatabase { pg_close($connection); // connect to the ownCloud database (dbname=$this->dbname) and check if it needs to be filled - $this->dbuser = \OC_Config::getValue('dbuser'); - $this->dbpassword = \OC_Config::getValue('dbpassword'); + $this->dbUser = \OC_Config::getValue('dbuser'); + $this->dbPassword = \OC_Config::getValue('dbpassword'); - $e_host = addslashes($this->dbhost); - $e_dbname = addslashes($this->dbname); - $e_user = addslashes($this->dbuser); - $e_password = addslashes($this->dbpassword); + $e_host = addslashes($this->dbHost); + $e_dbname = addslashes($this->dbName); + $e_user = addslashes($this->dbUser); + $e_password = addslashes($this->dbPassword); // Fix database with port connection if(strpos($e_host, ':')) { @@ -99,7 +99,7 @@ class PostgreSQL extends AbstractDatabase { throw new \OC\DatabaseSetupException($this->trans->t('PostgreSQL username and/or password not valid'), $this->trans->t('You need to enter either an existing account or the administrator.')); } - $query = "select count(*) FROM pg_class WHERE relname='".$this->tableprefix."users' limit 1"; + $query = "select count(*) FROM pg_class WHERE relname='".$this->tablePrefix."users' limit 1"; $result = pg_query($connection, $query); if($result) { $row = pg_fetch_row($result); @@ -111,8 +111,8 @@ class PostgreSQL extends AbstractDatabase { private function createDatabase($connection) { //we cant use OC_BD functions here because we need to connect as the administrative user. - $e_name = pg_escape_string($this->dbname); - $e_user = pg_escape_string($this->dbuser); + $e_name = pg_escape_string($this->dbName); + $e_user = pg_escape_string($this->dbUser); $query = "select datname from pg_database where datname = '$e_name'"; $result = pg_query($connection, $query); if(!$result) { @@ -137,8 +137,8 @@ class PostgreSQL extends AbstractDatabase { } private function createDBUser($connection) { - $e_name = pg_escape_string($this->dbuser); - $e_password = pg_escape_string($this->dbpassword); + $e_name = pg_escape_string($this->dbUser); + $e_password = pg_escape_string($this->dbPassword); $query = "select * from pg_roles where rolname='$e_name';"; $result = pg_query($connection, $query); if(!$result) { diff --git a/lib/private/util.php b/lib/private/util.php index 39d64952dc6..1b22e03ca6f 100644 --- a/lib/private/util.php +++ b/lib/private/util.php @@ -567,7 +567,8 @@ class OC_Util { } $webServerRestart = false; - $setup = new \OC\Setup($config, \OC::$server->getIniWrapper(), \OC::$server->getL10N('lib'), new \OC_Defaults()); + $setup = new \OC\Setup($config, \OC::$server->getIniWrapper(), \OC::$server->getL10N('lib'), + new \OC_Defaults(), \OC::$server->getLogger(), \OC::$server->getSecureRandom()); $availableDatabases = $setup->getSupportedDatabases(); if (empty($availableDatabases)) { $errors[] = array( diff --git a/settings/admin.php b/settings/admin.php index 3021a8d4d97..8410a6671ef 100644 --- a/settings/admin.php +++ b/settings/admin.php @@ -223,7 +223,7 @@ $formsAndMore = array_merge($formsAndMore, $formsMap); $formsAndMore[] = ['anchor' => 'backgroundjobs', 'section-name' => $l->t('Cron')]; $formsAndMore[] = ['anchor' => 'mail_general_settings', 'section-name' => $l->t('Email server')]; $formsAndMore[] = ['anchor' => 'log-section', 'section-name' => $l->t('Log')]; -$formsAndMore[] = ['anchor' => 'server-status', 'section-name' => $l->t('Server Status')]; +$formsAndMore[] = ['anchor' => 'server-status', 'section-name' => $l->t('Server status')]; $formsAndMore[] = ['anchor' => 'admin-tips', 'section-name' => $l->t('Tips & tricks')]; if ($updaterAppPanel) { $formsAndMore[] = ['anchor' => 'updater', 'section-name' => $l->t('Updates')]; diff --git a/settings/templates/admin.php b/settings/templates/admin.php index a55071bdf84..4203ee2cad7 100644 --- a/settings/templates/admin.php +++ b/settings/templates/admin.php @@ -507,6 +507,19 @@ if ($_['cronErrors']) { <?php endif; ?> </div> +<div class="section" id="server-status"> + <h2><?php p($l->t('Server status'));?></h2> + <ul> + <li> + <?php if ($_['fileLockingEnabled']) { + p($l->t('Transactional File Locking is enabled.')); + } else { + p($l->t('Transactional File Locking is disabled.')); + } ?> + </li> + </ul> +</div> + <div class="section" id="admin-tips"> <h2><?php p($l->t('Tips & tricks'));?></h2> <ul> @@ -528,18 +541,6 @@ if ($_['cronErrors']) { <li><a target="_blank" href="<?php p(link_to_docs('admin-security')); ?>"><?php p($l->t('Hardening and security guidance'));?> ↗</a></li> </ul> </div> -<div class="section" id="server-status"> - <h2><?php p($l->t('Server Status'));?></h2> - <ul> - <li> - <?php if ($_['fileLockingEnabled']) { - p($l->t('Transactional File Locking is enabled.')); - } else { - p($l->t('Transactional File Locking is disabled.')); - } ?> - </li> - </ul> -</div> <div class="section"> <h2><?php p($l->t('Version'));?></h2> diff --git a/tests/lib/appframework/utility/SimpleContainerTest.php b/tests/lib/appframework/utility/SimpleContainerTest.php index 09857808b9f..7ff579a85fc 100644 --- a/tests/lib/appframework/utility/SimpleContainerTest.php +++ b/tests/lib/appframework/utility/SimpleContainerTest.php @@ -136,8 +136,7 @@ class SimpleContainerTest extends \Test\TestCase { } - public function tesOverrideService() { - $this->container->registerParameter('test', 'abc'); + public function testOverrideService() { $this->container->registerService( 'Test\AppFramework\Utility\IInterfaceConstructor', function ($c) { return $c->query('Test\AppFramework\Utility\ClassSimpleConstructor'); @@ -147,10 +146,9 @@ class SimpleContainerTest extends \Test\TestCase { return $c->query('Test\AppFramework\Utility\ClassEmptyConstructor'); }); $object = $this->container->query( - 'Test\AppFramework\Utility\ClassInterfaceConstructor' + 'Test\AppFramework\Utility\IInterfaceConstructor' ); $this->assertTrue($object instanceof ClassEmptyConstructor); - $this->assertEquals('abc', $object->test); } public function testRegisterAliasParamter() { diff --git a/tests/lib/files/pathverificationtest.php b/tests/lib/files/pathverificationtest.php index b59aceba7b1..13fccd310f3 100644 --- a/tests/lib/files/pathverificationtest.php +++ b/tests/lib/files/pathverificationtest.php @@ -83,6 +83,10 @@ class PathVerification extends \Test\TestCase { return [ // this is the monkey emoji - http://en.wikipedia.org/w/index.php?title=%F0%9F%90%B5&redirect=no ['🐵'], + ['🐵.txt'], + ['txt.💩'], + ['💩🐵.txt'], + ['💩🐵'], ]; } diff --git a/tests/lib/setup.php b/tests/lib/setup.php index d07eaa40ee0..72c84520056 100644 --- a/tests/lib/setup.php +++ b/tests/lib/setup.php @@ -20,6 +20,10 @@ class Test_OC_Setup extends \Test\TestCase { private $defaults; /** @var \OC\Setup | PHPUnit_Framework_MockObject_MockObject */ protected $setupClass; + /** @var \OCP\ILogger | PHPUnit_Framework_MockObject_MockObject */ + protected $logger; + /** @var \OCP\Security\ISecureRandom | PHPUnit_Framework_MockObject_MockObject */ + protected $random; protected function setUp() { parent::setUp(); @@ -28,9 +32,11 @@ class Test_OC_Setup extends \Test\TestCase { $this->iniWrapper = $this->getMock('\bantu\IniGetWrapper\IniGetWrapper'); $this->l10n = $this->getMock('\OCP\IL10N'); $this->defaults = $this->getMock('\OC_Defaults'); + $this->logger = $this->getMock('\OCP\ILogger'); + $this->random = $this->getMock('\OCP\Security\ISecureRandom'); $this->setupClass = $this->getMock('\OC\Setup', - ['class_exists', 'is_callable'], - [$this->config, $this->iniWrapper, $this->l10n, $this->defaults]); + ['class_exists', 'is_callable', 'getAvailableDbDriversForPdo'], + [$this->config, $this->iniWrapper, $this->l10n, $this->defaults, $this->logger, $this->random]); } public function testGetSupportedDatabasesWithOneWorking() { @@ -45,9 +51,13 @@ class Test_OC_Setup extends \Test\TestCase { ->method('class_exists') ->will($this->returnValue(true)); $this->setupClass - ->expects($this->exactly(2)) + ->expects($this->once()) ->method('is_callable') ->will($this->returnValue(false)); + $this->setupClass + ->expects($this->once()) + ->method('getAvailableDbDriversForPdo') + ->will($this->returnValue([])); $result = $this->setupClass->getSupportedDatabases(); $expectedResult = array( 'sqlite' => 'SQLite' @@ -68,9 +78,13 @@ class Test_OC_Setup extends \Test\TestCase { ->method('class_exists') ->will($this->returnValue(false)); $this->setupClass - ->expects($this->exactly(3)) + ->expects($this->exactly(2)) ->method('is_callable') ->will($this->returnValue(false)); + $this->setupClass + ->expects($this->once()) + ->method('getAvailableDbDriversForPdo') + ->will($this->returnValue([])); $result = $this->setupClass->getSupportedDatabases(); $this->assertSame(array(), $result); @@ -88,9 +102,13 @@ class Test_OC_Setup extends \Test\TestCase { ->method('class_exists') ->will($this->returnValue(true)); $this->setupClass - ->expects($this->exactly(3)) + ->expects($this->exactly(2)) ->method('is_callable') ->will($this->returnValue(true)); + $this->setupClass + ->expects($this->once()) + ->method('getAvailableDbDriversForPdo') + ->will($this->returnValue(['mysql'])); $result = $this->setupClass->getSupportedDatabases(); $expectedResult = array( 'sqlite' => 'SQLite', |