Passed
Push — master ( 74dde7...58ef22 )
by Maurício
10:04 queued 10s
created

CheckUserPrivileges::getPrivileges()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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