Passed
Push — master ( aeb32e...81302f )
by Christoph
15:20 queued 10s
created

MySQL::createSpecificUser()   A

Complexity

Conditions 5
Paths 13

Size

Total Lines 49
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 27
nc 13
nop 2
dl 0
loc 49
rs 9.1768
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Hemanth Kumar Veeranki <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Michael Göhler <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Roeland Jago Douma <[email protected]>
13
 * @author Thomas Müller <[email protected]>
14
 * @author Vincent Petry <[email protected]>
15
 *
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program. If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OC\Setup;
33
34
use OC\DB\ConnectionAdapter;
35
use OC\DB\MySqlTools;
36
use OCP\IDBConnection;
37
use OCP\ILogger;
38
use Doctrine\DBAL\Platforms\MySQL80Platform;
39
40
class MySQL extends AbstractDatabase {
41
	public $dbprettyname = 'MySQL/MariaDB';
42
43
	public function setupDatabase($username) {
44
		//check if the database user has admin right
45
		$connection = $this->connect(['dbname' => null]);
46
47
		// detect mb4
48
		$tools = new MySqlTools();
49
		if ($tools->supports4ByteCharset(new ConnectionAdapter($connection))) {
50
			$this->config->setValue('mysql.utf8mb4', true);
51
			$connection = $this->connect(['dbname' => null]);
52
		}
53
54
		$this->createSpecificUser($username, new ConnectionAdapter($connection));
55
56
		//create the database
57
		$this->createDatabase($connection);
58
59
		//fill the database if needed
60
		$query = 'select count(*) from information_schema.tables where table_schema=? AND table_name = ?';
61
		$connection->executeQuery($query, [$this->dbName, $this->tablePrefix.'users']);
62
63
		$connection->close();
64
		$connection = $this->connect();
65
		try {
66
			$connection->connect();
67
		} catch (\Exception $e) {
68
			$this->logger->logException($e);
69
			throw new \OC\DatabaseSetupException($this->trans->t('MySQL username and/or password not valid'),
70
				$this->trans->t('You need to enter details of an existing account.'), 0, $e);
71
		}
72
	}
73
74
	/**
75
	 * @param \OC\DB\Connection $connection
76
	 */
77
	private function createDatabase($connection) {
78
		try {
79
			$name = $this->dbName;
80
			$user = $this->dbUser;
81
			//we can't use OC_DB functions here because we need to connect as the administrative user.
82
			$characterSet = $this->config->getValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8';
83
			$query = "CREATE DATABASE IF NOT EXISTS `$name` CHARACTER SET $characterSet COLLATE ${characterSet}_bin;";
84
			$connection->executeUpdate($query);
85
		} catch (\Exception $ex) {
86
			$this->logger->logException($ex, [
87
				'message' => 'Database creation failed.',
88
				'level' => ILogger::ERROR,
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

88
				'level' => /** @scrutinizer ignore-deprecated */ ILogger::ERROR,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
89
				'app' => 'mysql.setup',
90
			]);
91
			return;
92
		}
93
94
		try {
95
			//this query will fail if there aren't the right permissions, ignore the error
96
			$query = "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, REFERENCES, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EVENT, TRIGGER ON `$name` . * TO '$user'";
97
			$connection->executeUpdate($query);
98
		} catch (\Exception $ex) {
99
			$this->logger->logException($ex, [
100
				'message' => 'Could not automatically grant privileges, this can be ignored if database user already had privileges.',
101
				'level' => ILogger::DEBUG,
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::DEBUG has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

101
				'level' => /** @scrutinizer ignore-deprecated */ ILogger::DEBUG,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
102
				'app' => 'mysql.setup',
103
			]);
104
		}
105
	}
106
107
	/**
108
	 * @param IDBConnection $connection
109
	 * @throws \OC\DatabaseSetupException
110
	 */
111
	private function createDBUser($connection) {
112
		try {
113
			$name = $this->dbUser;
114
			$password = $this->dbPassword;
115
			// we need to create 2 accounts, one for global use and one for local user. if we don't specify the local one,
116
			// the anonymous user would take precedence when there is one.
117
118
			if ($connection->getDatabasePlatform() instanceof Mysql80Platform) {
119
				$query = "CREATE USER '$name'@'localhost' IDENTIFIED WITH mysql_native_password BY '$password'";
120
				$connection->executeUpdate($query);
121
				$query = "CREATE USER '$name'@'%' IDENTIFIED WITH mysql_native_password BY '$password'";
122
				$connection->executeUpdate($query);
123
			} else {
124
				$query = "CREATE USER '$name'@'localhost' IDENTIFIED BY '$password'";
125
				$connection->executeUpdate($query);
126
				$query = "CREATE USER '$name'@'%' IDENTIFIED BY '$password'";
127
				$connection->executeUpdate($query);
128
			}
129
		} catch (\Exception $ex) {
130
			$this->logger->logException($ex, [
131
				'message' => 'Database user creation failed.',
132
				'level' => ILogger::ERROR,
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

132
				'level' => /** @scrutinizer ignore-deprecated */ ILogger::ERROR,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
133
				'app' => 'mysql.setup',
134
			]);
135
		}
136
	}
137
138
	/**
139
	 * @param $username
140
	 * @param IDBConnection $connection
141
	 * @return array
142
	 */
143
	private function createSpecificUser($username, $connection) {
144
		try {
145
			//user already specified in config
146
			$oldUser = $this->config->getValue('dbuser', false);
147
148
			//we don't have a dbuser specified in config
149
			if ($this->dbUser !== $oldUser) {
150
				//add prefix to the admin username to prevent collisions
151
				$adminUser = substr('oc_' . $username, 0, 16);
152
153
				$i = 1;
154
				while (true) {
155
					//this should be enough to check for admin rights in mysql
156
					$query = 'SELECT user FROM mysql.user WHERE user=?';
157
					$result = $connection->executeQuery($query, [$adminUser]);
158
159
					//current dbuser has admin rights
160
					$data = $result->fetchAll();
161
					$result->closeCursor();
162
					//new dbuser does not exist
163
					if (count($data) === 0) {
164
						//use the admin login data for the new database user
165
						$this->dbUser = $adminUser;
166
167
						//create a random password so we don't need to store the admin password in the config file
168
						$this->dbPassword = $this->random->generate(30);
169
170
						$this->createDBUser($connection);
171
172
						break;
173
					} else {
174
						//repeat with different username
175
						$length = strlen((string)$i);
176
						$adminUser = substr('oc_' . $username, 0, 16 - $length) . $i;
177
						$i++;
178
					}
179
				}
180
			}
181
		} catch (\Exception $ex) {
182
			$this->logger->logException($ex, [
183
				'message' => 'Can not create a new MySQL user, will continue with the provided user.',
184
				'level' => ILogger::INFO,
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::INFO has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

184
				'level' => /** @scrutinizer ignore-deprecated */ ILogger::INFO,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
185
				'app' => 'mysql.setup',
186
			]);
187
		}
188
189
		$this->config->setValues([
190
			'dbuser' => $this->dbUser,
191
			'dbpassword' => $this->dbPassword,
192
		]);
193
	}
194
}
195