Passed
Push — master ( c6c512...8bc9e2 )
by Robin
16:17 queued 13s
created
lib/private/Setup/PostgreSQL.php 2 patches
Indentation   +150 added lines, -150 removed lines patch added patch discarded remove patch
@@ -34,154 +34,154 @@
 block discarded – undo
34 34
 use OCP\Security\ISecureRandom;
35 35
 
36 36
 class PostgreSQL extends AbstractDatabase {
37
-	public $dbprettyname = 'PostgreSQL';
38
-
39
-	/**
40
-	 * @param string $username
41
-	 * @throws \OC\DatabaseSetupException
42
-	 */
43
-	public function setupDatabase($username) {
44
-		try {
45
-			$connection = $this->connect([
46
-				'dbname' => 'postgres'
47
-			]);
48
-			if ($this->tryCreateDbUser) {
49
-				//check for roles creation rights in postgresql
50
-				$builder = $connection->getQueryBuilder();
51
-				$builder->automaticTablePrefix(false);
52
-				$query = $builder
53
-					->select('rolname')
54
-					->from('pg_roles')
55
-					->where($builder->expr()->eq('rolcreaterole', new Literal('TRUE')))
56
-					->andWhere($builder->expr()->eq('rolname', $builder->createNamedParameter($this->dbUser)));
57
-
58
-				try {
59
-					$result = $query->execute();
60
-					$canCreateRoles = $result->rowCount() > 0;
61
-				} catch (DatabaseException $e) {
62
-					$canCreateRoles = false;
63
-				}
64
-
65
-				if ($canCreateRoles) {
66
-					$connectionMainDatabase = $this->connect();
67
-					//use the admin login data for the new database user
68
-
69
-					//add prefix to the postgresql user name to prevent collisions
70
-					$this->dbUser = 'oc_' . strtolower($username);
71
-					//create a new password so we don't need to store the admin config in the config file
72
-					$this->dbPassword = \OC::$server->getSecureRandom()->generate(30, ISecureRandom::CHAR_ALPHANUMERIC);
73
-
74
-					$this->createDBUser($connection);
75
-
76
-					// Go to the main database and grant create on the public schema
77
-					// The code below is implemented to make installing possible with PostgreSQL version 15:
78
-					// https://www.postgresql.org/docs/release/15.0/
79
-					// From the release notes: For new databases having no need to defend against insider threats, granting CREATE permission will yield the behavior of prior releases
80
-					// Therefore we assume that the database is only used by one user/service which is Nextcloud
81
-					// Additional services should get installed in a separate database in order to stay secure
82
-					// Also see https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PATTERNS
83
-					$connectionMainDatabase->executeQuery('GRANT CREATE ON SCHEMA public TO ' . addslashes($this->dbUser));
84
-					$connectionMainDatabase->close();
85
-				}
86
-			}
87
-
88
-			$this->config->setValues([
89
-				'dbuser' => $this->dbUser,
90
-				'dbpassword' => $this->dbPassword,
91
-			]);
92
-
93
-			//create the database
94
-			$this->createDatabase($connection);
95
-			// the connection to dbname=postgres is not needed anymore
96
-			$connection->close();
97
-		} catch (\Exception $e) {
98
-			$this->logger->warning('Error trying to connect as "postgres", assuming database is setup and tables need to be created', [
99
-				'exception' => $e,
100
-			]);
101
-			$this->config->setValues([
102
-				'dbuser' => $this->dbUser,
103
-				'dbpassword' => $this->dbPassword,
104
-			]);
105
-		}
106
-
107
-		// connect to the database (dbname=$this->dbname) and check if it needs to be filled
108
-		$this->dbUser = $this->config->getValue('dbuser');
109
-		$this->dbPassword = $this->config->getValue('dbpassword');
110
-		$connection = $this->connect();
111
-		try {
112
-			$connection->connect();
113
-		} catch (\Exception $e) {
114
-			$this->logger->error($e->getMessage(), [
115
-				'exception' => $e,
116
-			]);
117
-			throw new \OC\DatabaseSetupException($this->trans->t('PostgreSQL username and/or password not valid'),
118
-				$this->trans->t('You need to enter details of an existing account.'), 0, $e);
119
-		}
120
-	}
121
-
122
-	private function createDatabase(Connection $connection) {
123
-		if (!$this->databaseExists($connection)) {
124
-			//The database does not exists... let's create it
125
-			$query = $connection->prepare("CREATE DATABASE " . addslashes($this->dbName) . " OWNER " . addslashes($this->dbUser));
126
-			try {
127
-				$query->execute();
128
-			} catch (DatabaseException $e) {
129
-				$this->logger->error('Error while trying to create database', [
130
-					'exception' => $e,
131
-				]);
132
-			}
133
-		} else {
134
-			$query = $connection->prepare("REVOKE ALL PRIVILEGES ON DATABASE " . addslashes($this->dbName) . " FROM PUBLIC");
135
-			try {
136
-				$query->execute();
137
-			} catch (DatabaseException $e) {
138
-				$this->logger->error('Error while trying to restrict database permissions', [
139
-					'exception' => $e,
140
-				]);
141
-			}
142
-		}
143
-	}
144
-
145
-	private function userExists(Connection $connection) {
146
-		$builder = $connection->getQueryBuilder();
147
-		$builder->automaticTablePrefix(false);
148
-		$query = $builder->select('*')
149
-			->from('pg_roles')
150
-			->where($builder->expr()->eq('rolname', $builder->createNamedParameter($this->dbUser)));
151
-		$result = $query->execute();
152
-		return $result->rowCount() > 0;
153
-	}
154
-
155
-	private function databaseExists(Connection $connection) {
156
-		$builder = $connection->getQueryBuilder();
157
-		$builder->automaticTablePrefix(false);
158
-		$query = $builder->select('datname')
159
-			->from('pg_database')
160
-			->where($builder->expr()->eq('datname', $builder->createNamedParameter($this->dbName)));
161
-		$result = $query->execute();
162
-		return $result->rowCount() > 0;
163
-	}
164
-
165
-	private function createDBUser(Connection $connection) {
166
-		$dbUser = $this->dbUser;
167
-		try {
168
-			$i = 1;
169
-			while ($this->userExists($connection)) {
170
-				$i++;
171
-				$this->dbUser = $dbUser . $i;
172
-			}
173
-
174
-			// create the user
175
-			$query = $connection->prepare("CREATE USER " . addslashes($this->dbUser) . " CREATEDB PASSWORD '" . addslashes($this->dbPassword) . "'");
176
-			$query->execute();
177
-			if ($this->databaseExists($connection)) {
178
-				$query = $connection->prepare('GRANT CONNECT ON DATABASE ' . addslashes($this->dbName) . ' TO '.addslashes($this->dbUser));
179
-				$query->execute();
180
-			}
181
-		} catch (DatabaseException $e) {
182
-			$this->logger->error('Error while trying to create database user', [
183
-				'exception' => $e,
184
-			]);
185
-		}
186
-	}
37
+    public $dbprettyname = 'PostgreSQL';
38
+
39
+    /**
40
+     * @param string $username
41
+     * @throws \OC\DatabaseSetupException
42
+     */
43
+    public function setupDatabase($username) {
44
+        try {
45
+            $connection = $this->connect([
46
+                'dbname' => 'postgres'
47
+            ]);
48
+            if ($this->tryCreateDbUser) {
49
+                //check for roles creation rights in postgresql
50
+                $builder = $connection->getQueryBuilder();
51
+                $builder->automaticTablePrefix(false);
52
+                $query = $builder
53
+                    ->select('rolname')
54
+                    ->from('pg_roles')
55
+                    ->where($builder->expr()->eq('rolcreaterole', new Literal('TRUE')))
56
+                    ->andWhere($builder->expr()->eq('rolname', $builder->createNamedParameter($this->dbUser)));
57
+
58
+                try {
59
+                    $result = $query->execute();
60
+                    $canCreateRoles = $result->rowCount() > 0;
61
+                } catch (DatabaseException $e) {
62
+                    $canCreateRoles = false;
63
+                }
64
+
65
+                if ($canCreateRoles) {
66
+                    $connectionMainDatabase = $this->connect();
67
+                    //use the admin login data for the new database user
68
+
69
+                    //add prefix to the postgresql user name to prevent collisions
70
+                    $this->dbUser = 'oc_' . strtolower($username);
71
+                    //create a new password so we don't need to store the admin config in the config file
72
+                    $this->dbPassword = \OC::$server->getSecureRandom()->generate(30, ISecureRandom::CHAR_ALPHANUMERIC);
73
+
74
+                    $this->createDBUser($connection);
75
+
76
+                    // Go to the main database and grant create on the public schema
77
+                    // The code below is implemented to make installing possible with PostgreSQL version 15:
78
+                    // https://www.postgresql.org/docs/release/15.0/
79
+                    // From the release notes: For new databases having no need to defend against insider threats, granting CREATE permission will yield the behavior of prior releases
80
+                    // Therefore we assume that the database is only used by one user/service which is Nextcloud
81
+                    // Additional services should get installed in a separate database in order to stay secure
82
+                    // Also see https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PATTERNS
83
+                    $connectionMainDatabase->executeQuery('GRANT CREATE ON SCHEMA public TO ' . addslashes($this->dbUser));
84
+                    $connectionMainDatabase->close();
85
+                }
86
+            }
87
+
88
+            $this->config->setValues([
89
+                'dbuser' => $this->dbUser,
90
+                'dbpassword' => $this->dbPassword,
91
+            ]);
92
+
93
+            //create the database
94
+            $this->createDatabase($connection);
95
+            // the connection to dbname=postgres is not needed anymore
96
+            $connection->close();
97
+        } catch (\Exception $e) {
98
+            $this->logger->warning('Error trying to connect as "postgres", assuming database is setup and tables need to be created', [
99
+                'exception' => $e,
100
+            ]);
101
+            $this->config->setValues([
102
+                'dbuser' => $this->dbUser,
103
+                'dbpassword' => $this->dbPassword,
104
+            ]);
105
+        }
106
+
107
+        // connect to the database (dbname=$this->dbname) and check if it needs to be filled
108
+        $this->dbUser = $this->config->getValue('dbuser');
109
+        $this->dbPassword = $this->config->getValue('dbpassword');
110
+        $connection = $this->connect();
111
+        try {
112
+            $connection->connect();
113
+        } catch (\Exception $e) {
114
+            $this->logger->error($e->getMessage(), [
115
+                'exception' => $e,
116
+            ]);
117
+            throw new \OC\DatabaseSetupException($this->trans->t('PostgreSQL username and/or password not valid'),
118
+                $this->trans->t('You need to enter details of an existing account.'), 0, $e);
119
+        }
120
+    }
121
+
122
+    private function createDatabase(Connection $connection) {
123
+        if (!$this->databaseExists($connection)) {
124
+            //The database does not exists... let's create it
125
+            $query = $connection->prepare("CREATE DATABASE " . addslashes($this->dbName) . " OWNER " . addslashes($this->dbUser));
126
+            try {
127
+                $query->execute();
128
+            } catch (DatabaseException $e) {
129
+                $this->logger->error('Error while trying to create database', [
130
+                    'exception' => $e,
131
+                ]);
132
+            }
133
+        } else {
134
+            $query = $connection->prepare("REVOKE ALL PRIVILEGES ON DATABASE " . addslashes($this->dbName) . " FROM PUBLIC");
135
+            try {
136
+                $query->execute();
137
+            } catch (DatabaseException $e) {
138
+                $this->logger->error('Error while trying to restrict database permissions', [
139
+                    'exception' => $e,
140
+                ]);
141
+            }
142
+        }
143
+    }
144
+
145
+    private function userExists(Connection $connection) {
146
+        $builder = $connection->getQueryBuilder();
147
+        $builder->automaticTablePrefix(false);
148
+        $query = $builder->select('*')
149
+            ->from('pg_roles')
150
+            ->where($builder->expr()->eq('rolname', $builder->createNamedParameter($this->dbUser)));
151
+        $result = $query->execute();
152
+        return $result->rowCount() > 0;
153
+    }
154
+
155
+    private function databaseExists(Connection $connection) {
156
+        $builder = $connection->getQueryBuilder();
157
+        $builder->automaticTablePrefix(false);
158
+        $query = $builder->select('datname')
159
+            ->from('pg_database')
160
+            ->where($builder->expr()->eq('datname', $builder->createNamedParameter($this->dbName)));
161
+        $result = $query->execute();
162
+        return $result->rowCount() > 0;
163
+    }
164
+
165
+    private function createDBUser(Connection $connection) {
166
+        $dbUser = $this->dbUser;
167
+        try {
168
+            $i = 1;
169
+            while ($this->userExists($connection)) {
170
+                $i++;
171
+                $this->dbUser = $dbUser . $i;
172
+            }
173
+
174
+            // create the user
175
+            $query = $connection->prepare("CREATE USER " . addslashes($this->dbUser) . " CREATEDB PASSWORD '" . addslashes($this->dbPassword) . "'");
176
+            $query->execute();
177
+            if ($this->databaseExists($connection)) {
178
+                $query = $connection->prepare('GRANT CONNECT ON DATABASE ' . addslashes($this->dbName) . ' TO '.addslashes($this->dbUser));
179
+                $query->execute();
180
+            }
181
+        } catch (DatabaseException $e) {
182
+            $this->logger->error('Error while trying to create database user', [
183
+                'exception' => $e,
184
+            ]);
185
+        }
186
+    }
187 187
 }
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -67,7 +67,7 @@  discard block
 block discarded – undo
67 67
 					//use the admin login data for the new database user
68 68
 
69 69
 					//add prefix to the postgresql user name to prevent collisions
70
-					$this->dbUser = 'oc_' . strtolower($username);
70
+					$this->dbUser = 'oc_'.strtolower($username);
71 71
 					//create a new password so we don't need to store the admin config in the config file
72 72
 					$this->dbPassword = \OC::$server->getSecureRandom()->generate(30, ISecureRandom::CHAR_ALPHANUMERIC);
73 73
 
@@ -80,7 +80,7 @@  discard block
 block discarded – undo
80 80
 					// Therefore we assume that the database is only used by one user/service which is Nextcloud
81 81
 					// Additional services should get installed in a separate database in order to stay secure
82 82
 					// Also see https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PATTERNS
83
-					$connectionMainDatabase->executeQuery('GRANT CREATE ON SCHEMA public TO ' . addslashes($this->dbUser));
83
+					$connectionMainDatabase->executeQuery('GRANT CREATE ON SCHEMA public TO '.addslashes($this->dbUser));
84 84
 					$connectionMainDatabase->close();
85 85
 				}
86 86
 			}
@@ -122,7 +122,7 @@  discard block
 block discarded – undo
122 122
 	private function createDatabase(Connection $connection) {
123 123
 		if (!$this->databaseExists($connection)) {
124 124
 			//The database does not exists... let's create it
125
-			$query = $connection->prepare("CREATE DATABASE " . addslashes($this->dbName) . " OWNER " . addslashes($this->dbUser));
125
+			$query = $connection->prepare("CREATE DATABASE ".addslashes($this->dbName)." OWNER ".addslashes($this->dbUser));
126 126
 			try {
127 127
 				$query->execute();
128 128
 			} catch (DatabaseException $e) {
@@ -131,7 +131,7 @@  discard block
 block discarded – undo
131 131
 				]);
132 132
 			}
133 133
 		} else {
134
-			$query = $connection->prepare("REVOKE ALL PRIVILEGES ON DATABASE " . addslashes($this->dbName) . " FROM PUBLIC");
134
+			$query = $connection->prepare("REVOKE ALL PRIVILEGES ON DATABASE ".addslashes($this->dbName)." FROM PUBLIC");
135 135
 			try {
136 136
 				$query->execute();
137 137
 			} catch (DatabaseException $e) {
@@ -168,14 +168,14 @@  discard block
 block discarded – undo
168 168
 			$i = 1;
169 169
 			while ($this->userExists($connection)) {
170 170
 				$i++;
171
-				$this->dbUser = $dbUser . $i;
171
+				$this->dbUser = $dbUser.$i;
172 172
 			}
173 173
 
174 174
 			// create the user
175
-			$query = $connection->prepare("CREATE USER " . addslashes($this->dbUser) . " CREATEDB PASSWORD '" . addslashes($this->dbPassword) . "'");
175
+			$query = $connection->prepare("CREATE USER ".addslashes($this->dbUser)." CREATEDB PASSWORD '".addslashes($this->dbPassword)."'");
176 176
 			$query->execute();
177 177
 			if ($this->databaseExists($connection)) {
178
-				$query = $connection->prepare('GRANT CONNECT ON DATABASE ' . addslashes($this->dbName) . ' TO '.addslashes($this->dbUser));
178
+				$query = $connection->prepare('GRANT CONNECT ON DATABASE '.addslashes($this->dbName).' TO '.addslashes($this->dbUser));
179 179
 				$query->execute();
180 180
 			}
181 181
 		} catch (DatabaseException $e) {
Please login to merge, or discard this patch.
lib/private/Setup/MySQL.php 1 patch
Indentation   +167 added lines, -167 removed lines patch added patch discarded remove patch
@@ -36,171 +36,171 @@
 block discarded – undo
36 36
 use OCP\Security\ISecureRandom;
37 37
 
38 38
 class MySQL extends AbstractDatabase {
39
-	public $dbprettyname = 'MySQL/MariaDB';
40
-
41
-	public function setupDatabase($username) {
42
-		//check if the database user has admin right
43
-		$connection = $this->connect(['dbname' => null]);
44
-
45
-		// detect mb4
46
-		$tools = new MySqlTools();
47
-		if ($tools->supports4ByteCharset(new ConnectionAdapter($connection))) {
48
-			$this->config->setValue('mysql.utf8mb4', true);
49
-			$connection = $this->connect(['dbname' => null]);
50
-		}
51
-
52
-		if ($this->tryCreateDbUser) {
53
-			$this->createSpecificUser($username, new ConnectionAdapter($connection));
54
-		}
55
-
56
-		$this->config->setValues([
57
-			'dbuser' => $this->dbUser,
58
-			'dbpassword' => $this->dbPassword,
59
-		]);
60
-
61
-		//create the database
62
-		$this->createDatabase($connection);
63
-
64
-		//fill the database if needed
65
-		$query = 'select count(*) from information_schema.tables where table_schema=? AND table_name = ?';
66
-		$connection->executeQuery($query, [$this->dbName, $this->tablePrefix.'users']);
67
-
68
-		$connection->close();
69
-		$connection = $this->connect();
70
-		try {
71
-			$connection->connect();
72
-		} catch (\Exception $e) {
73
-			$this->logger->error($e->getMessage(), [
74
-				'exception' => $e,
75
-			]);
76
-			throw new \OC\DatabaseSetupException($this->trans->t('MySQL username and/or password not valid'),
77
-				$this->trans->t('You need to enter details of an existing account.'), 0, $e);
78
-		}
79
-	}
80
-
81
-	/**
82
-	 * @param \OC\DB\Connection $connection
83
-	 */
84
-	private function createDatabase($connection) {
85
-		try {
86
-			$name = $this->dbName;
87
-			$user = $this->dbUser;
88
-			//we can't use OC_DB functions here because we need to connect as the administrative user.
89
-			$characterSet = $this->config->getValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8';
90
-			$query = "CREATE DATABASE IF NOT EXISTS `$name` CHARACTER SET $characterSet COLLATE {$characterSet}_bin;";
91
-			$connection->executeUpdate($query);
92
-		} catch (\Exception $ex) {
93
-			$this->logger->error('Database creation failed.', [
94
-				'exception' => $ex,
95
-				'app' => 'mysql.setup',
96
-			]);
97
-			return;
98
-		}
99
-
100
-		try {
101
-			//this query will fail if there aren't the right permissions, ignore the error
102
-			$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'";
103
-			$connection->executeUpdate($query);
104
-		} catch (\Exception $ex) {
105
-			$this->logger->debug('Could not automatically grant privileges, this can be ignored if database user already had privileges.', [
106
-				'exception' => $ex,
107
-				'app' => 'mysql.setup',
108
-			]);
109
-		}
110
-	}
111
-
112
-	/**
113
-	 * @param IDBConnection $connection
114
-	 * @throws \OC\DatabaseSetupException
115
-	 */
116
-	private function createDBUser($connection) {
117
-		try {
118
-			$name = $this->dbUser;
119
-			$password = $this->dbPassword;
120
-			// we need to create 2 accounts, one for global use and one for local user. if we don't specify the local one,
121
-			// the anonymous user would take precedence when there is one.
122
-
123
-			if ($connection->getDatabasePlatform() instanceof Mysql80Platform) {
124
-				$query = "CREATE USER '$name'@'localhost' IDENTIFIED WITH mysql_native_password BY '$password'";
125
-				$connection->executeUpdate($query);
126
-				$query = "CREATE USER '$name'@'%' IDENTIFIED WITH mysql_native_password BY '$password'";
127
-				$connection->executeUpdate($query);
128
-			} else {
129
-				$query = "CREATE USER '$name'@'localhost' IDENTIFIED BY '$password'";
130
-				$connection->executeUpdate($query);
131
-				$query = "CREATE USER '$name'@'%' IDENTIFIED BY '$password'";
132
-				$connection->executeUpdate($query);
133
-			}
134
-		} catch (\Exception $ex) {
135
-			$this->logger->error('Database user creation failed.', [
136
-				'exception' => $ex,
137
-				'app' => 'mysql.setup',
138
-			]);
139
-			throw $ex;
140
-		}
141
-	}
142
-
143
-	/**
144
-	 * @param $username
145
-	 * @param IDBConnection $connection
146
-	 */
147
-	private function createSpecificUser($username, $connection): void {
148
-		$rootUser = $this->dbUser;
149
-		$rootPassword = $this->dbPassword;
150
-
151
-		//create a random password so we don't need to store the admin password in the config file
152
-		$saveSymbols = str_replace(['\"', '\\', '\'', '`'], '', ISecureRandom::CHAR_SYMBOLS);
153
-		$password = $this->random->generate(22, ISecureRandom::CHAR_ALPHANUMERIC . $saveSymbols)
154
-			. $this->random->generate(2, ISecureRandom::CHAR_UPPER)
155
-			. $this->random->generate(2, ISecureRandom::CHAR_LOWER)
156
-			. $this->random->generate(2, ISecureRandom::CHAR_DIGITS)
157
-			. $this->random->generate(2, $saveSymbols);
158
-		$this->dbPassword = str_shuffle($password);
159
-
160
-		try {
161
-			//user already specified in config
162
-			$oldUser = $this->config->getValue('dbuser', false);
163
-
164
-			//we don't have a dbuser specified in config
165
-			if ($this->dbUser !== $oldUser) {
166
-				//add prefix to the admin username to prevent collisions
167
-				$adminUser = substr('oc_' . $username, 0, 16);
168
-
169
-				$i = 1;
170
-				while (true) {
171
-					//this should be enough to check for admin rights in mysql
172
-					$query = 'SELECT user FROM mysql.user WHERE user=?';
173
-					$result = $connection->executeQuery($query, [$adminUser]);
174
-
175
-					//current dbuser has admin rights
176
-					$data = $result->fetchAll();
177
-					$result->closeCursor();
178
-					//new dbuser does not exist
179
-					if (count($data) === 0) {
180
-						//use the admin login data for the new database user
181
-						$this->dbUser = $adminUser;
182
-						$this->createDBUser($connection);
183
-
184
-						break;
185
-					} else {
186
-						//repeat with different username
187
-						$length = strlen((string)$i);
188
-						$adminUser = substr('oc_' . $username, 0, 16 - $length) . $i;
189
-						$i++;
190
-					}
191
-				}
192
-			} else {
193
-				// Reuse existing password if a database config is already present
194
-				$this->dbPassword = $rootPassword;
195
-			}
196
-		} catch (\Exception $ex) {
197
-			$this->logger->info('Can not create a new MySQL user, will continue with the provided user.', [
198
-				'exception' => $ex,
199
-				'app' => 'mysql.setup',
200
-			]);
201
-			// Restore the original credentials
202
-			$this->dbUser = $rootUser;
203
-			$this->dbPassword = $rootPassword;
204
-		}
205
-	}
39
+    public $dbprettyname = 'MySQL/MariaDB';
40
+
41
+    public function setupDatabase($username) {
42
+        //check if the database user has admin right
43
+        $connection = $this->connect(['dbname' => null]);
44
+
45
+        // detect mb4
46
+        $tools = new MySqlTools();
47
+        if ($tools->supports4ByteCharset(new ConnectionAdapter($connection))) {
48
+            $this->config->setValue('mysql.utf8mb4', true);
49
+            $connection = $this->connect(['dbname' => null]);
50
+        }
51
+
52
+        if ($this->tryCreateDbUser) {
53
+            $this->createSpecificUser($username, new ConnectionAdapter($connection));
54
+        }
55
+
56
+        $this->config->setValues([
57
+            'dbuser' => $this->dbUser,
58
+            'dbpassword' => $this->dbPassword,
59
+        ]);
60
+
61
+        //create the database
62
+        $this->createDatabase($connection);
63
+
64
+        //fill the database if needed
65
+        $query = 'select count(*) from information_schema.tables where table_schema=? AND table_name = ?';
66
+        $connection->executeQuery($query, [$this->dbName, $this->tablePrefix.'users']);
67
+
68
+        $connection->close();
69
+        $connection = $this->connect();
70
+        try {
71
+            $connection->connect();
72
+        } catch (\Exception $e) {
73
+            $this->logger->error($e->getMessage(), [
74
+                'exception' => $e,
75
+            ]);
76
+            throw new \OC\DatabaseSetupException($this->trans->t('MySQL username and/or password not valid'),
77
+                $this->trans->t('You need to enter details of an existing account.'), 0, $e);
78
+        }
79
+    }
80
+
81
+    /**
82
+     * @param \OC\DB\Connection $connection
83
+     */
84
+    private function createDatabase($connection) {
85
+        try {
86
+            $name = $this->dbName;
87
+            $user = $this->dbUser;
88
+            //we can't use OC_DB functions here because we need to connect as the administrative user.
89
+            $characterSet = $this->config->getValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8';
90
+            $query = "CREATE DATABASE IF NOT EXISTS `$name` CHARACTER SET $characterSet COLLATE {$characterSet}_bin;";
91
+            $connection->executeUpdate($query);
92
+        } catch (\Exception $ex) {
93
+            $this->logger->error('Database creation failed.', [
94
+                'exception' => $ex,
95
+                'app' => 'mysql.setup',
96
+            ]);
97
+            return;
98
+        }
99
+
100
+        try {
101
+            //this query will fail if there aren't the right permissions, ignore the error
102
+            $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'";
103
+            $connection->executeUpdate($query);
104
+        } catch (\Exception $ex) {
105
+            $this->logger->debug('Could not automatically grant privileges, this can be ignored if database user already had privileges.', [
106
+                'exception' => $ex,
107
+                'app' => 'mysql.setup',
108
+            ]);
109
+        }
110
+    }
111
+
112
+    /**
113
+     * @param IDBConnection $connection
114
+     * @throws \OC\DatabaseSetupException
115
+     */
116
+    private function createDBUser($connection) {
117
+        try {
118
+            $name = $this->dbUser;
119
+            $password = $this->dbPassword;
120
+            // we need to create 2 accounts, one for global use and one for local user. if we don't specify the local one,
121
+            // the anonymous user would take precedence when there is one.
122
+
123
+            if ($connection->getDatabasePlatform() instanceof Mysql80Platform) {
124
+                $query = "CREATE USER '$name'@'localhost' IDENTIFIED WITH mysql_native_password BY '$password'";
125
+                $connection->executeUpdate($query);
126
+                $query = "CREATE USER '$name'@'%' IDENTIFIED WITH mysql_native_password BY '$password'";
127
+                $connection->executeUpdate($query);
128
+            } else {
129
+                $query = "CREATE USER '$name'@'localhost' IDENTIFIED BY '$password'";
130
+                $connection->executeUpdate($query);
131
+                $query = "CREATE USER '$name'@'%' IDENTIFIED BY '$password'";
132
+                $connection->executeUpdate($query);
133
+            }
134
+        } catch (\Exception $ex) {
135
+            $this->logger->error('Database user creation failed.', [
136
+                'exception' => $ex,
137
+                'app' => 'mysql.setup',
138
+            ]);
139
+            throw $ex;
140
+        }
141
+    }
142
+
143
+    /**
144
+     * @param $username
145
+     * @param IDBConnection $connection
146
+     */
147
+    private function createSpecificUser($username, $connection): void {
148
+        $rootUser = $this->dbUser;
149
+        $rootPassword = $this->dbPassword;
150
+
151
+        //create a random password so we don't need to store the admin password in the config file
152
+        $saveSymbols = str_replace(['\"', '\\', '\'', '`'], '', ISecureRandom::CHAR_SYMBOLS);
153
+        $password = $this->random->generate(22, ISecureRandom::CHAR_ALPHANUMERIC . $saveSymbols)
154
+            . $this->random->generate(2, ISecureRandom::CHAR_UPPER)
155
+            . $this->random->generate(2, ISecureRandom::CHAR_LOWER)
156
+            . $this->random->generate(2, ISecureRandom::CHAR_DIGITS)
157
+            . $this->random->generate(2, $saveSymbols);
158
+        $this->dbPassword = str_shuffle($password);
159
+
160
+        try {
161
+            //user already specified in config
162
+            $oldUser = $this->config->getValue('dbuser', false);
163
+
164
+            //we don't have a dbuser specified in config
165
+            if ($this->dbUser !== $oldUser) {
166
+                //add prefix to the admin username to prevent collisions
167
+                $adminUser = substr('oc_' . $username, 0, 16);
168
+
169
+                $i = 1;
170
+                while (true) {
171
+                    //this should be enough to check for admin rights in mysql
172
+                    $query = 'SELECT user FROM mysql.user WHERE user=?';
173
+                    $result = $connection->executeQuery($query, [$adminUser]);
174
+
175
+                    //current dbuser has admin rights
176
+                    $data = $result->fetchAll();
177
+                    $result->closeCursor();
178
+                    //new dbuser does not exist
179
+                    if (count($data) === 0) {
180
+                        //use the admin login data for the new database user
181
+                        $this->dbUser = $adminUser;
182
+                        $this->createDBUser($connection);
183
+
184
+                        break;
185
+                    } else {
186
+                        //repeat with different username
187
+                        $length = strlen((string)$i);
188
+                        $adminUser = substr('oc_' . $username, 0, 16 - $length) . $i;
189
+                        $i++;
190
+                    }
191
+                }
192
+            } else {
193
+                // Reuse existing password if a database config is already present
194
+                $this->dbPassword = $rootPassword;
195
+            }
196
+        } catch (\Exception $ex) {
197
+            $this->logger->info('Can not create a new MySQL user, will continue with the provided user.', [
198
+                'exception' => $ex,
199
+                'app' => 'mysql.setup',
200
+            ]);
201
+            // Restore the original credentials
202
+            $this->dbUser = $rootUser;
203
+            $this->dbPassword = $rootPassword;
204
+        }
205
+    }
206 206
 }
Please login to merge, or discard this patch.
lib/private/Setup/AbstractDatabase.php 1 patch
Indentation   +109 added lines, -109 removed lines patch added patch discarded remove patch
@@ -37,124 +37,124 @@
 block discarded – undo
37 37
 use Psr\Log\LoggerInterface;
38 38
 
39 39
 abstract class AbstractDatabase {
40
-	/** @var IL10N */
41
-	protected $trans;
42
-	/** @var string */
43
-	protected $dbUser;
44
-	/** @var string */
45
-	protected $dbPassword;
46
-	/** @var string */
47
-	protected $dbName;
48
-	/** @var string */
49
-	protected $dbHost;
50
-	/** @var string */
51
-	protected $dbPort;
52
-	/** @var string */
53
-	protected $tablePrefix;
54
-	/** @var SystemConfig */
55
-	protected $config;
56
-	/** @var LoggerInterface */
57
-	protected $logger;
58
-	/** @var ISecureRandom */
59
-	protected $random;
60
-	/** @var bool */
61
-	protected $tryCreateDbUser;
40
+    /** @var IL10N */
41
+    protected $trans;
42
+    /** @var string */
43
+    protected $dbUser;
44
+    /** @var string */
45
+    protected $dbPassword;
46
+    /** @var string */
47
+    protected $dbName;
48
+    /** @var string */
49
+    protected $dbHost;
50
+    /** @var string */
51
+    protected $dbPort;
52
+    /** @var string */
53
+    protected $tablePrefix;
54
+    /** @var SystemConfig */
55
+    protected $config;
56
+    /** @var LoggerInterface */
57
+    protected $logger;
58
+    /** @var ISecureRandom */
59
+    protected $random;
60
+    /** @var bool */
61
+    protected $tryCreateDbUser;
62 62
 
63
-	public function __construct(IL10N $trans, SystemConfig $config, LoggerInterface $logger, ISecureRandom $random) {
64
-		$this->trans = $trans;
65
-		$this->config = $config;
66
-		$this->logger = $logger;
67
-		$this->random = $random;
68
-	}
63
+    public function __construct(IL10N $trans, SystemConfig $config, LoggerInterface $logger, ISecureRandom $random) {
64
+        $this->trans = $trans;
65
+        $this->config = $config;
66
+        $this->logger = $logger;
67
+        $this->random = $random;
68
+    }
69 69
 
70
-	public function validate($config) {
71
-		$errors = [];
72
-		if (empty($config['dbuser']) && empty($config['dbname'])) {
73
-			$errors[] = $this->trans->t("Enter the database username and name for %s", [$this->dbprettyname]);
74
-		} elseif (empty($config['dbuser'])) {
75
-			$errors[] = $this->trans->t("Enter the database username for %s", [$this->dbprettyname]);
76
-		} elseif (empty($config['dbname'])) {
77
-			$errors[] = $this->trans->t("Enter the database name for %s", [$this->dbprettyname]);
78
-		}
79
-		if (substr_count($config['dbname'], '.') >= 1) {
80
-			$errors[] = $this->trans->t("You cannot use dots in the database name %s", [$this->dbprettyname]);
81
-		}
82
-		return $errors;
83
-	}
70
+    public function validate($config) {
71
+        $errors = [];
72
+        if (empty($config['dbuser']) && empty($config['dbname'])) {
73
+            $errors[] = $this->trans->t("Enter the database username and name for %s", [$this->dbprettyname]);
74
+        } elseif (empty($config['dbuser'])) {
75
+            $errors[] = $this->trans->t("Enter the database username for %s", [$this->dbprettyname]);
76
+        } elseif (empty($config['dbname'])) {
77
+            $errors[] = $this->trans->t("Enter the database name for %s", [$this->dbprettyname]);
78
+        }
79
+        if (substr_count($config['dbname'], '.') >= 1) {
80
+            $errors[] = $this->trans->t("You cannot use dots in the database name %s", [$this->dbprettyname]);
81
+        }
82
+        return $errors;
83
+    }
84 84
 
85
-	public function initialize($config) {
86
-		$dbUser = $config['dbuser'];
87
-		$dbPass = $config['dbpass'];
88
-		$dbName = $config['dbname'];
89
-		$dbHost = !empty($config['dbhost']) ? $config['dbhost'] : 'localhost';
90
-		$dbPort = !empty($config['dbport']) ? $config['dbport'] : '';
91
-		$dbTablePrefix = isset($config['dbtableprefix']) ? $config['dbtableprefix'] : 'oc_';
85
+    public function initialize($config) {
86
+        $dbUser = $config['dbuser'];
87
+        $dbPass = $config['dbpass'];
88
+        $dbName = $config['dbname'];
89
+        $dbHost = !empty($config['dbhost']) ? $config['dbhost'] : 'localhost';
90
+        $dbPort = !empty($config['dbport']) ? $config['dbport'] : '';
91
+        $dbTablePrefix = isset($config['dbtableprefix']) ? $config['dbtableprefix'] : 'oc_';
92 92
 
93
-		$createUserConfig = $this->config->getValue("setup_create_db_user", true);
94
-		// accept `false` both as bool and string, since setting config values from env will result in a string
95
-		$this->tryCreateDbUser = $createUserConfig !== false && $createUserConfig !== "false";
93
+        $createUserConfig = $this->config->getValue("setup_create_db_user", true);
94
+        // accept `false` both as bool and string, since setting config values from env will result in a string
95
+        $this->tryCreateDbUser = $createUserConfig !== false && $createUserConfig !== "false";
96 96
 
97
-		$this->config->setValues([
98
-			'dbname' => $dbName,
99
-			'dbhost' => $dbHost,
100
-			'dbport' => $dbPort,
101
-			'dbtableprefix' => $dbTablePrefix,
102
-		]);
97
+        $this->config->setValues([
98
+            'dbname' => $dbName,
99
+            'dbhost' => $dbHost,
100
+            'dbport' => $dbPort,
101
+            'dbtableprefix' => $dbTablePrefix,
102
+        ]);
103 103
 
104
-		$this->dbUser = $dbUser;
105
-		$this->dbPassword = $dbPass;
106
-		$this->dbName = $dbName;
107
-		$this->dbHost = $dbHost;
108
-		$this->dbPort = $dbPort;
109
-		$this->tablePrefix = $dbTablePrefix;
110
-	}
104
+        $this->dbUser = $dbUser;
105
+        $this->dbPassword = $dbPass;
106
+        $this->dbName = $dbName;
107
+        $this->dbHost = $dbHost;
108
+        $this->dbPort = $dbPort;
109
+        $this->tablePrefix = $dbTablePrefix;
110
+    }
111 111
 
112
-	/**
113
-	 * @param array $configOverwrite
114
-	 * @return \OC\DB\Connection
115
-	 */
116
-	protected function connect(array $configOverwrite = []): Connection {
117
-		$connectionParams = [
118
-			'host' => $this->dbHost,
119
-			'user' => $this->dbUser,
120
-			'password' => $this->dbPassword,
121
-			'tablePrefix' => $this->tablePrefix,
122
-			'dbname' => $this->dbName
123
-		];
112
+    /**
113
+     * @param array $configOverwrite
114
+     * @return \OC\DB\Connection
115
+     */
116
+    protected function connect(array $configOverwrite = []): Connection {
117
+        $connectionParams = [
118
+            'host' => $this->dbHost,
119
+            'user' => $this->dbUser,
120
+            'password' => $this->dbPassword,
121
+            'tablePrefix' => $this->tablePrefix,
122
+            'dbname' => $this->dbName
123
+        ];
124 124
 
125
-		// adding port support through installer
126
-		if (!empty($this->dbPort)) {
127
-			if (ctype_digit($this->dbPort)) {
128
-				$connectionParams['port'] = $this->dbPort;
129
-			} else {
130
-				$connectionParams['unix_socket'] = $this->dbPort;
131
-			}
132
-		} elseif (strpos($this->dbHost, ':')) {
133
-			// Host variable may carry a port or socket.
134
-			[$host, $portOrSocket] = explode(':', $this->dbHost, 2);
135
-			if (ctype_digit($portOrSocket)) {
136
-				$connectionParams['port'] = $portOrSocket;
137
-			} else {
138
-				$connectionParams['unix_socket'] = $portOrSocket;
139
-			}
140
-			$connectionParams['host'] = $host;
141
-		}
125
+        // adding port support through installer
126
+        if (!empty($this->dbPort)) {
127
+            if (ctype_digit($this->dbPort)) {
128
+                $connectionParams['port'] = $this->dbPort;
129
+            } else {
130
+                $connectionParams['unix_socket'] = $this->dbPort;
131
+            }
132
+        } elseif (strpos($this->dbHost, ':')) {
133
+            // Host variable may carry a port or socket.
134
+            [$host, $portOrSocket] = explode(':', $this->dbHost, 2);
135
+            if (ctype_digit($portOrSocket)) {
136
+                $connectionParams['port'] = $portOrSocket;
137
+            } else {
138
+                $connectionParams['unix_socket'] = $portOrSocket;
139
+            }
140
+            $connectionParams['host'] = $host;
141
+        }
142 142
 
143
-		$connectionParams = array_merge($connectionParams, $configOverwrite);
144
-		$cf = new ConnectionFactory($this->config);
145
-		return $cf->getConnection($this->config->getValue('dbtype', 'sqlite'), $connectionParams);
146
-	}
143
+        $connectionParams = array_merge($connectionParams, $configOverwrite);
144
+        $cf = new ConnectionFactory($this->config);
145
+        return $cf->getConnection($this->config->getValue('dbtype', 'sqlite'), $connectionParams);
146
+    }
147 147
 
148
-	/**
149
-	 * @param string $username
150
-	 */
151
-	abstract public function setupDatabase($username);
148
+    /**
149
+     * @param string $username
150
+     */
151
+    abstract public function setupDatabase($username);
152 152
 
153
-	public function runMigrations() {
154
-		if (!is_dir(\OC::$SERVERROOT."/core/Migrations")) {
155
-			return;
156
-		}
157
-		$ms = new MigrationService('core', \OC::$server->get(Connection::class));
158
-		$ms->migrate('latest', true);
159
-	}
153
+    public function runMigrations() {
154
+        if (!is_dir(\OC::$SERVERROOT."/core/Migrations")) {
155
+            return;
156
+        }
157
+        $ms = new MigrationService('core', \OC::$server->get(Connection::class));
158
+        $ms->migrate('latest', true);
159
+    }
160 160
 }
Please login to merge, or discard this patch.