Passed
Push — master ( a7be49...5129e4 )
by Maurício
08:55
created

libraries/classes/CheckUserPrivileges.php (1 issue)

1
<?php
2
/**
3
 * Get user's global privileges and some db-specific privileges
4
 */
5
6
declare(strict_types=1);
7
8
namespace PhpMyAdmin;
9
10
use PhpMyAdmin\Query\Utilities;
11
use function mb_strpos;
12
use function mb_substr;
13
use function preg_match;
14
use function preg_replace;
15
use function strpos;
16
17
/**
18
 * PhpMyAdmin\CheckUserPrivileges class
19
 */
20
class CheckUserPrivileges
21
{
22
    /** @var DatabaseInterface */
23
    private $dbi;
24
25
    /**
26
     * @param DatabaseInterface $dbi DatabaseInterface object
27
     */
28 348
    public function __construct(DatabaseInterface $dbi)
29
    {
30 348
        $this->dbi = $dbi;
31 348
    }
32
33
    /**
34
     * Extracts details from a result row of a SHOW GRANT query
35
     *
36
     * @param string $row grant row
37
     *
38
     * @return array
39
     */
40 8
    public function getItemsFromShowGrantsRow(string $row): array
41
    {
42 8
        $db_name_offset = mb_strpos($row, ' ON ') + 4;
43
44 8
        $tblname_end_offset = mb_strpos($row, ' TO ');
45 8
        $tblname_start_offset = false;
46 8
        $__tblname_start_offset = mb_strpos($row, '`.', $db_name_offset);
47
48 8
        if ($__tblname_start_offset && $__tblname_start_offset < $tblname_end_offset) {
49 8
            $tblname_start_offset = $__tblname_start_offset + 1;
50
        }
51
52 8
        if (! $tblname_start_offset) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tblname_start_offset of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
53 8
            $tblname_start_offset = mb_strpos($row, '.', $db_name_offset);
54
        }
55
56 8
        $show_grants_dbname = mb_substr(
57 8
            $row,
58 8
            $db_name_offset,
59 8
            $tblname_start_offset - $db_name_offset
60
        );
61
62 8
        $show_grants_dbname = Util::unQuote($show_grants_dbname, '`');
63
64 8
        $show_grants_str = mb_substr(
65 8
            $row,
66 8
            6,
67 8
            mb_strpos($row, ' ON ') - 6
68
        );
69
70 8
        $show_grants_tblname = mb_substr(
71 8
            $row,
72 8
            $tblname_start_offset + 1,
73 8
            $tblname_end_offset - $tblname_start_offset - 1
74
        );
75 8
        $show_grants_tblname = Util::unQuote($show_grants_tblname, '`');
76
77
        return [
78 8
            $show_grants_str,
79 8
            $show_grants_dbname,
80 8
            $show_grants_tblname,
81
        ];
82
    }
83
84
    /**
85
     * Check if user has required privileges for
86
     * performing 'Adjust privileges' operations
87
     *
88
     * @param string $show_grants_str     string containing grants for user
89
     * @param string $show_grants_dbname  name of db extracted from grant string
90
     * @param string $show_grants_tblname name of table extracted from grant string
91
     */
92 4
    public function checkRequiredPrivilegesForAdjust(
93
        string $show_grants_str,
94
        string $show_grants_dbname,
95
        string $show_grants_tblname
96
    ): void {
97
        // '... ALL PRIVILEGES ON *.* ...' OR '... ALL PRIVILEGES ON `mysql`.* ..'
98
        // OR
99
        // SELECT, INSERT, UPDATE, DELETE .... ON *.* OR `mysql`.*
100 4
        if ($show_grants_str != 'ALL'
101 4
            && $show_grants_str != 'ALL PRIVILEGES'
102 4
            && (mb_strpos(
103 4
                $show_grants_str,
104 4
                'SELECT, INSERT, UPDATE, DELETE'
105 4
            ) === false)
106
        ) {
107
            return;
108
        }
109
110 4
        if ($show_grants_dbname == '*'
111 4
            && $show_grants_tblname == '*'
112
        ) {
113 4
            $GLOBALS['col_priv'] = true;
114 4
            $GLOBALS['db_priv'] = true;
115 4
            $GLOBALS['proc_priv'] = true;
116 4
            $GLOBALS['table_priv'] = true;
117
118 4
            if ($show_grants_str == 'ALL PRIVILEGES'
119 4
                || $show_grants_str == 'ALL'
120
            ) {
121 4
                $GLOBALS['is_reload_priv'] = true;
122
            }
123
        }
124
125
        // check for specific tables in `mysql` db
126
        // Ex. '... ALL PRIVILEGES on `mysql`.`columns_priv` .. '
127 4
        if ($show_grants_dbname != 'mysql') {
128 4
            return;
129
        }
130
131 3
        switch ($show_grants_tblname) {
132 4
            case 'columns_priv':
133
                $GLOBALS['col_priv'] = true;
134
                break;
135 4
            case 'db':
136 4
                $GLOBALS['db_priv'] = true;
137 4
                break;
138 4
            case 'procs_priv':
139
                $GLOBALS['proc_priv'] = true;
140
                break;
141 4
            case 'tables_priv':
142
                $GLOBALS['table_priv'] = true;
143
                break;
144 4
            case '*':
145 4
                $GLOBALS['col_priv'] = true;
146 4
                $GLOBALS['db_priv'] = true;
147 4
                $GLOBALS['proc_priv'] = true;
148 4
                $GLOBALS['table_priv'] = true;
149 4
                break;
150
            default:
151
        }
152 4
    }
153
154
    /**
155
     * sets privilege information extracted from SHOW GRANTS result
156
     *
157
     * Detection for some CREATE privilege.
158
     *
159
     * Since MySQL 4.1.2, we can easily detect current user's grants using $userlink
160
     * (no control user needed) and we don't have to try any other method for
161
     * detection
162
     *
163
     * @todo fix to get really all privileges, not only explicitly defined for this user
164
     * from MySQL manual: (https://dev.mysql.com/doc/refman/5.0/en/show-grants.html)
165
     * SHOW GRANTS displays only the privileges granted explicitly to the named
166
     * account. Other privileges might be available to the account, but they are not
167
     * displayed. For example, if an anonymous account exists, the named account
168
     * might be able to use its privileges, but SHOW GRANTS will not display them.
169
     */
170 300
    private function analyseShowGrant(): void
171
    {
172 300
        if (Util::cacheExists('is_create_db_priv')) {
173 40
            $GLOBALS['is_create_db_priv'] = Util::cacheGet(
174 40
                'is_create_db_priv'
175
            );
176 40
            $GLOBALS['is_reload_priv'] = Util::cacheGet(
177 40
                'is_reload_priv'
178
            );
179 40
            $GLOBALS['db_to_create'] = Util::cacheGet(
180 40
                'db_to_create'
181
            );
182 40
            $GLOBALS['dbs_where_create_table_allowed'] = Util::cacheGet(
183 40
                'dbs_where_create_table_allowed'
184
            );
185 40
            $GLOBALS['dbs_to_test'] = Util::cacheGet(
186 40
                'dbs_to_test'
187
            );
188
189 40
            $GLOBALS['db_priv'] = Util::cacheGet(
190 40
                'db_priv'
191
            );
192 40
            $GLOBALS['col_priv'] = Util::cacheGet(
193 40
                'col_priv'
194
            );
195 40
            $GLOBALS['table_priv'] = Util::cacheGet(
196 40
                'table_priv'
197
            );
198 40
            $GLOBALS['proc_priv'] = Util::cacheGet(
199 40
                'proc_priv'
200
            );
201
202 40
            return;
203
        }
204
205
        // defaults
206 300
        $GLOBALS['is_create_db_priv']  = false;
207 300
        $GLOBALS['is_reload_priv'] = false;
208 300
        $GLOBALS['db_to_create'] = '';
209 300
        $GLOBALS['dbs_where_create_table_allowed'] = [];
210 300
        $GLOBALS['dbs_to_test'] = Utilities::getSystemSchemas();
211 300
        $GLOBALS['proc_priv'] = false;
212 300
        $GLOBALS['db_priv'] = false;
213 300
        $GLOBALS['col_priv'] = false;
214 300
        $GLOBALS['table_priv'] = false;
215
216 300
        $rs_usr = $this->dbi->tryQuery('SHOW GRANTS');
217
218 300
        if (! $rs_usr) {
219
            return;
220
        }
221
222 300
        $re0 = '(^|(\\\\\\\\)+|[^\\\\])'; // non-escaped wildcards
223 300
        $re1 = '(^|[^\\\\])(\\\)+'; // escaped wildcards
224
225 300
        while ($row = $this->dbi->fetchRow($rs_usr)) {
226
            [
227
                $show_grants_str,
228
                $show_grants_dbname,
229
                $show_grants_tblname,
230
            ] = $this->getItemsFromShowGrantsRow($row[0]);
231
232
            if ($show_grants_dbname == '*') {
233
                if ($show_grants_str != 'USAGE') {
234
                    $GLOBALS['dbs_to_test'] = false;
235
                }
236
            } elseif ($GLOBALS['dbs_to_test'] !== false) {
237
                $GLOBALS['dbs_to_test'][] = $show_grants_dbname;
238
            }
239
240
            if (mb_strpos($show_grants_str, 'RELOAD') !== false) {
241
                $GLOBALS['is_reload_priv'] = true;
242
            }
243
244
            // check for the required privileges for adjust
245
            $this->checkRequiredPrivilegesForAdjust(
246
                $show_grants_str,
247
                $show_grants_dbname,
248
                $show_grants_tblname
249
            );
250
251
            /**
252
             * @todo if we find CREATE VIEW but not CREATE, do not offer
253
             * the create database dialog box
254
             */
255
            if ($show_grants_str == 'ALL'
256
                || $show_grants_str == 'ALL PRIVILEGES'
257
                || $show_grants_str == 'CREATE'
258
                || strpos($show_grants_str, 'CREATE,') !== false
259
            ) {
260
                if ($show_grants_dbname == '*') {
261
                    // a global CREATE privilege
262
                    $GLOBALS['is_create_db_priv'] = true;
263
                    $GLOBALS['is_reload_priv'] = true;
264
                    $GLOBALS['db_to_create']   = '';
265
                    $GLOBALS['dbs_where_create_table_allowed'][] = '*';
266
                    // @todo we should not break here, cause GRANT ALL *.*
267
                    // could be revoked by a later rule like GRANT SELECT ON db.*
268
                    break;
269
                }
270
271
                // this array may contain wildcards
272
                $GLOBALS['dbs_where_create_table_allowed'][] = $show_grants_dbname;
273
274
                $dbname_to_test = Util::backquote($show_grants_dbname);
275
276
                if ($GLOBALS['is_create_db_priv']) {
277
                    // no need for any more tests if we already know this
278
                    continue;
279
                }
280
281
                // does this db exist?
282
                if ((preg_match('/' . $re0 . '%|_/', $show_grants_dbname)
283
                    && ! preg_match('/\\\\%|\\\\_/', $show_grants_dbname))
284
                    || (! $this->dbi->tryQuery(
285
                        'USE ' . preg_replace(
286
                            '/' . $re1 . '(%|_)/',
287
                            '\\1\\3',
288
                            $dbname_to_test
289
                        )
290
                    )
291
                    && mb_substr($this->dbi->getError(), 1, 4) != 1044)
292
                ) {
293
                    /**
294
                     * Do not handle the underscore wildcard
295
                     * (this case must be rare anyway)
296
                     */
297
                    $GLOBALS['db_to_create'] = preg_replace(
298
                        '/' . $re0 . '%/',
299
                        '\\1',
300
                        $show_grants_dbname
301
                    );
302
                    $GLOBALS['db_to_create'] = preg_replace(
303
                        '/' . $re1 . '(%|_)/',
304
                        '\\1\\3',
305
                        $GLOBALS['db_to_create']
306
                    );
307
                    $GLOBALS['is_create_db_priv'] = true;
308
309
                    /**
310
                     * @todo collect $GLOBALS['db_to_create'] into an array,
311
                     * to display a drop-down in the "Create database" dialog
312
                     */
313
                     // we don't break, we want all possible databases
314
                     //break;
315
                } // end if // end elseif
316
            } // end if
317
        } // end while
318
319 300
        $this->dbi->freeResult($rs_usr);
320
321
        // must also cacheUnset() them in
322
        // PhpMyAdmin\Plugins\Auth\AuthenticationCookie
323 300
        Util::cacheSet('is_create_db_priv', $GLOBALS['is_create_db_priv']);
324 300
        Util::cacheSet('is_reload_priv', $GLOBALS['is_reload_priv']);
325 300
        Util::cacheSet('db_to_create', $GLOBALS['db_to_create']);
326 300
        Util::cacheSet(
327 300
            'dbs_where_create_table_allowed',
328 300
            $GLOBALS['dbs_where_create_table_allowed']
329
        );
330 300
        Util::cacheSet('dbs_to_test', $GLOBALS['dbs_to_test']);
331
332 300
        Util::cacheSet('proc_priv', $GLOBALS['proc_priv']);
333 300
        Util::cacheSet('table_priv', $GLOBALS['table_priv']);
334 300
        Util::cacheSet('col_priv', $GLOBALS['col_priv']);
335 300
        Util::cacheSet('db_priv', $GLOBALS['db_priv']);
336 300
    }
337
338
    /**
339
     * Get user's global privileges and some db-specific privileges
340
     */
341 340
    public function getPrivileges(): void
342
    {
343 340
        $username = '';
344
345 340
        $current = $this->dbi->getCurrentUserAndHost();
346 340
        if (! empty($current)) {
347 300
            [$username] = $current;
348
        }
349
350
        // If MySQL is started with --skip-grant-tables
351 340
        if ($username === '') {
352 52
            $GLOBALS['is_create_db_priv'] = true;
353 52
            $GLOBALS['is_reload_priv'] = true;
354 52
            $GLOBALS['db_to_create'] = '';
355 52
            $GLOBALS['dbs_where_create_table_allowed'] = ['*'];
356 52
            $GLOBALS['dbs_to_test'] = false;
357 52
            $GLOBALS['db_priv'] = true;
358 52
            $GLOBALS['col_priv'] = true;
359 52
            $GLOBALS['table_priv'] = true;
360 52
            $GLOBALS['proc_priv'] = true;
361
        } else {
362 300
            $this->analyseShowGrant();
363
        }
364 340
    }
365
}
366