Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 74 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
MysqlCreateUserTask | |
0.00% |
0 / 74 |
|
0.00% |
0 / 6 |
342 | |
0.00% |
0 / 1 |
getName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDependencies | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isSkipped | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
execute | |
0.00% |
0 / 60 |
|
0.00% |
0 / 1 |
132 | |||
buildFullUserName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
userDefinitelyExists | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Installer\Task; |
4 | |
5 | use MediaWiki\MainConfigNames; |
6 | use MediaWiki\Status\Status; |
7 | use Wikimedia\Rdbms\DatabaseFactory; |
8 | use Wikimedia\Rdbms\DBConnectionError; |
9 | use Wikimedia\Rdbms\DBQueryError; |
10 | use Wikimedia\Rdbms\IMaintainableDatabase; |
11 | |
12 | /** |
13 | * Create the MySQL/MariaDB user |
14 | * |
15 | * @internal For use by the installer |
16 | */ |
17 | class MysqlCreateUserTask extends Task { |
18 | public function getName() { |
19 | return 'user'; |
20 | } |
21 | |
22 | public function getDependencies() { |
23 | return 'database'; |
24 | } |
25 | |
26 | public function isSkipped(): bool { |
27 | $dbUser = $this->getConfigVar( MainConfigNames::DBuser ); |
28 | return $dbUser == $this->getOption( 'InstallUser' ) |
29 | || $this->getOption( 'InstallUser' ) === null; |
30 | } |
31 | |
32 | public function execute(): Status { |
33 | $dbUser = $this->getConfigVar( MainConfigNames::DBuser ); |
34 | $status = $this->getConnection( ITaskContext::CONN_CREATE_DATABASE ); |
35 | if ( !$status->isOK() ) { |
36 | return $status; |
37 | } |
38 | |
39 | $conn = $status->getDB(); |
40 | $server = $this->getConfigVar( MainConfigNames::DBserver ); |
41 | $password = $this->getConfigVar( MainConfigNames::DBpassword ); |
42 | $grantableNames = []; |
43 | |
44 | if ( $this->getOption( 'CreateDBAccount' ) ) { |
45 | // Before we blindly try to create a user that already has access, |
46 | try { // first attempt to connect to the database |
47 | ( new DatabaseFactory() )->create( 'mysql', [ |
48 | 'host' => $server, |
49 | 'user' => $dbUser, |
50 | 'password' => $password, |
51 | 'ssl' => $this->getConfigVar( MainConfigNames::DBssl ), |
52 | 'dbname' => null, |
53 | 'flags' => 0, |
54 | 'tablePrefix' => $this->getConfigVar( MainConfigNames::DBprefix ) |
55 | ] ); |
56 | $grantableNames[] = $this->buildFullUserName( $conn, $dbUser, $server ); |
57 | $tryToCreate = false; |
58 | } catch ( DBConnectionError $e ) { |
59 | $tryToCreate = true; |
60 | } |
61 | } else { |
62 | $grantableNames[] = $this->buildFullUserName( $conn, $dbUser, $server ); |
63 | $tryToCreate = false; |
64 | } |
65 | |
66 | if ( $tryToCreate ) { |
67 | $createHostList = [ |
68 | $server, |
69 | 'localhost', |
70 | 'localhost.localdomain', |
71 | '%' |
72 | ]; |
73 | |
74 | $createHostList = array_unique( $createHostList ); |
75 | $escPass = $conn->addQuotes( $password ); |
76 | |
77 | foreach ( $createHostList as $host ) { |
78 | $fullName = $this->buildFullUserName( $conn, $dbUser, $host ); |
79 | if ( !$this->userDefinitelyExists( $conn, $host, $dbUser ) ) { |
80 | try { |
81 | $conn->begin( __METHOD__ ); |
82 | $conn->query( "CREATE USER $fullName IDENTIFIED BY $escPass", __METHOD__ ); |
83 | $conn->commit( __METHOD__ ); |
84 | $grantableNames[] = $fullName; |
85 | } catch ( DBQueryError $dqe ) { |
86 | if ( $conn->lastErrno() == 1396 /* ER_CANNOT_USER */ ) { |
87 | // User (probably) already exists |
88 | $conn->rollback( __METHOD__ ); |
89 | $status->warning( 'config-install-user-alreadyexists', $dbUser ); |
90 | $grantableNames[] = $fullName; |
91 | break; |
92 | } else { |
93 | // If we couldn't create for some bizarre reason and the |
94 | // user probably doesn't exist, skip the grant |
95 | $conn->rollback( __METHOD__ ); |
96 | $status->warning( 'config-install-user-create-failed', $dbUser, $dqe->getMessage() ); |
97 | } |
98 | } |
99 | } else { |
100 | $status->warning( 'config-install-user-alreadyexists', $dbUser ); |
101 | $grantableNames[] = $fullName; |
102 | break; |
103 | } |
104 | } |
105 | } |
106 | |
107 | // Try to grant to all the users we know exist or we were able to create |
108 | $dbAllTables = $conn->addIdentifierQuotes( $this->getConfigVar( MainConfigNames::DBname ) ) . '.*'; |
109 | foreach ( $grantableNames as $name ) { |
110 | try { |
111 | $conn->begin( __METHOD__ ); |
112 | $conn->query( "GRANT ALL PRIVILEGES ON $dbAllTables TO $name", __METHOD__ ); |
113 | $conn->commit( __METHOD__ ); |
114 | } catch ( DBQueryError $dqe ) { |
115 | $conn->rollback( __METHOD__ ); |
116 | $status->fatal( 'config-install-user-grant-failed', $dbUser, $dqe->getMessage() ); |
117 | } |
118 | } |
119 | |
120 | return $status; |
121 | } |
122 | |
123 | /** |
124 | * Return a formal 'User'@'Host' username for use in queries |
125 | * @param IMaintainableDatabase $conn |
126 | * @param string $name Username, quotes will be added |
127 | * @param string $host Hostname, quotes will be added |
128 | * @return string |
129 | */ |
130 | private function buildFullUserName( $conn, $name, $host ) { |
131 | return $conn->addQuotes( $name ) . '@' . $conn->addQuotes( $host ); |
132 | } |
133 | |
134 | /** |
135 | * Try to see if the user account exists. Our "superuser" may not have |
136 | * access to mysql.user, so false means "no" or "maybe" |
137 | * @param IMaintainableDatabase $conn |
138 | * @param string $host Hostname to check |
139 | * @param string $user Username to check |
140 | * @return bool |
141 | */ |
142 | private function userDefinitelyExists( $conn, $host, $user ) { |
143 | try { |
144 | $res = $conn->newSelectQueryBuilder() |
145 | ->select( [ 'Host', 'User' ] ) |
146 | ->from( 'mysql.user' ) |
147 | ->where( [ 'Host' => $host, 'User' => $user ] ) |
148 | ->caller( __METHOD__ )->fetchRow(); |
149 | |
150 | return (bool)$res; |
151 | } catch ( DBQueryError $dqe ) { |
152 | return false; |
153 | } |
154 | } |
155 | |
156 | } |