1<?php 2/** 3 * @author Bart Visscher <bartv@thisnet.nl> 4 * @author Joas Schilling <coding@schilljs.com> 5 * @author Michael Göhler <somebody.here@gmx.de> 6 * @author Morris Jobke <hey@morrisjobke.de> 7 * @author Robin McCorkell <robin@mccorkell.me.uk> 8 * @author Roeland Jago Douma <rullzer@owncloud.com> 9 * @author Thomas Müller <thomas.mueller@tmit.eu> 10 * @author Vincent Petry <pvince81@owncloud.com> 11 * 12 * @copyright Copyright (c) 2018, ownCloud GmbH 13 * @license AGPL-3.0 14 * 15 * This code is free software: you can redistribute it and/or modify 16 * it under the terms of the GNU Affero General Public License, version 3, 17 * as published by the Free Software Foundation. 18 * 19 * This program is distributed in the hope that it will be useful, 20 * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 * GNU Affero General Public License for more details. 23 * 24 * You should have received a copy of the GNU Affero General Public License, version 3, 25 * along with this program. If not, see <http://www.gnu.org/licenses/> 26 * 27 */ 28namespace OC\Setup; 29 30use OC\DB\ConnectionFactory; 31use OC\DB\MySqlTools; 32use OCP\IDBConnection; 33 34class MySQL extends AbstractDatabase { 35 public $dbprettyname = 'MySQL/MariaDB'; 36 37 public function setupDatabase($username) { 38 //check if the database user has admin right 39 $connection = $this->connect(); 40 41 // detect mb4 42 $tools = new MySqlTools(); 43 if ($tools->supports4ByteCharset($connection)) { 44 $this->config->setSystemValue('mysql.utf8mb4', true); 45 $connection = $this->connect(); 46 } 47 48 $this->createSpecificUser($username, $connection); 49 50 //create the database 51 $this->createDatabase($connection); 52 53 //fill the database if needed 54 $query='select count(*) from information_schema.tables where table_schema=? AND table_name = ?'; 55 $result = $connection->executeQuery($query, [$this->dbName, $this->tablePrefix.'users']); 56 $row = $result->fetch(); 57 if (!$row or $row['count(*)'] === '0') { 58 \OC_DB::createDbFromStructure($this->dbDefinitionFile); 59 } 60 } 61 62 /** 63 * @param \OC\DB\Connection $connection 64 */ 65 private function createDatabase($connection) { 66 try { 67 $name = $this->dbName; 68 $user = $this->dbUser; 69 //we can't use OC_DB functions here because we need to connect as the administrative user. 70 $characterSet = $this->config->getSystemValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8'; 71 $query = "CREATE DATABASE IF NOT EXISTS `$name` CHARACTER SET $characterSet COLLATE ${characterSet}_bin;"; 72 $connection->executeUpdate($query); 73 } catch (\Exception $ex) { 74 $this->logger->error('Database creation failed: {error}', [ 75 'app' => 'mysql.setup', 76 'error' => $ex->getMessage() 77 ]); 78 return; 79 } 80 81 try { 82 //this query will fail if there aren't the right permissions, ignore the error 83 $query="GRANT ALL PRIVILEGES ON `$name` . * TO '$user'"; 84 $connection->executeUpdate($query); 85 } catch (\Exception $ex) { 86 $this->logger->debug('Could not automatically grant privileges, this can be ignored if database user already had privileges: {error}', [ 87 'app' => 'mysql.setup', 88 'error' => $ex->getMessage() 89 ]); 90 } 91 } 92 93 /** 94 * @param IDBConnection $connection 95 * @throws \OC\DatabaseSetupException 96 */ 97 private function createDBUser($connection) { 98 $name = $this->dbUser; 99 $password = $this->dbPassword; 100 // we need to create 2 accounts, one for global use and one for local user. if we don't specify the local one, 101 // the anonymous user would take precedence when there is one. 102 $query = "CREATE USER '$name'@'localhost' IDENTIFIED BY '$password'"; 103 $connection->executeUpdate($query); 104 $query = "CREATE USER '$name'@'%' IDENTIFIED BY '$password'"; 105 $connection->executeUpdate($query); 106 } 107 108 /** 109 * @return \OC\DB\Connection 110 * @throws \OC\DatabaseSetupException 111 */ 112 private function connect() { 113 $connectionParams = [ 114 'host' => $this->dbHost, 115 'user' => $this->dbUser, 116 'password' => $this->dbPassword, 117 'tablePrefix' => $this->tablePrefix, 118 ]; 119 120 // adding port support 121 if (\strpos($this->dbHost, ':')) { 122 // Host variable may carry a port or socket. 123 list($host, $portOrSocket) = \explode(':', $this->dbHost, 2); 124 if (\ctype_digit($portOrSocket)) { 125 $connectionParams['port'] = $portOrSocket; 126 } else { 127 $connectionParams['unix_socket'] = $portOrSocket; 128 } 129 $connectionParams['host'] = $host; 130 } 131 132 $systemConfig = \OC::$server->getSystemConfig(); 133 $cf = new ConnectionFactory($systemConfig); 134 return $cf->getConnection('mysql', $connectionParams); 135 } 136 137 /** 138 * @param $username 139 * @param IDBConnection $connection 140 * @return array 141 */ 142 private function createSpecificUser($username, $connection) { 143 try { 144 //user already specified in config 145 $oldUser = $this->config->getSystemValue('dbuser', false); 146 147 //we don't have a dbuser specified in config 148 if ($this->dbUser !== $oldUser) { 149 //add prefix to the admin username to prevent collisions 150 $adminUser = \substr('oc_' . $username, 0, 16); 151 152 $i = 1; 153 while (true) { 154 //this should be enough to check for admin rights in mysql 155 $query = 'SELECT user FROM mysql.user WHERE user=?'; 156 $result = $connection->executeQuery($query, [$adminUser]); 157 158 //current dbuser has admin rights 159 if ($result) { 160 $data = $result->fetchAll(); 161 //new dbuser does not exist 162 if (\count($data) === 0) { 163 //use the admin login data for the new database user 164 $this->dbUser = $adminUser; 165 166 //create a random password so we don't need to store the admin password in the config file 167 $this->dbPassword = $this->random->generate(30); 168 169 $this->createDBUser($connection); 170 171 break; 172 } else { 173 //repeat with different username 174 $length = \strlen((string)$i); 175 $adminUser = \substr('oc_' . $username, 0, 16 - $length) . $i; 176 $i++; 177 } 178 } else { 179 break; 180 } 181 }; 182 } 183 } catch (\Exception $ex) { 184 $this->logger->error('Specific user creation failed: {error}', [ 185 'app' => 'mysql.setup', 186 'error' => $ex->getMessage() 187 ]); 188 } 189 190 $this->config->setSystemValues([ 191 'dbuser' => $this->dbUser, 192 'dbpassword' => $this->dbPassword, 193 ]); 194 } 195} 196