Passed
Push — main ( d7d864...d7e834 )
by Thierry
01:48
created

UserAdminTrait::getUserGrants()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 1
b 0
f 0
nc 2
nop 3
dl 0
loc 14
rs 10
1
<?php
2
3
namespace Lagdo\DbAdmin\Db\Traits;
4
5
use function explode;
6
use function in_array;
7
use function array_keys;
8
use function strtoupper;
9
use function preg_match;
10
use function preg_match_all;
11
12
trait UserAdminTrait
13
{
14
    /**
15
     * Get the users and hosts
16
     *
17
     * @param string $database  The database name
18
     *
19
     * @return array
20
     */
21
    public function getUsers(string $database): array
22
    {
23
        // From privileges.inc.php
24
        $clause = ($database == '' ? 'user' : 'db WHERE ' . $this->driver->quote($database) . ' LIKE Db');
25
        $query = "SELECT User, Host FROM mysql.$clause ORDER BY Host, User";
26
        $statement = $this->driver->query($query);
27
        // $grant = $statement;
28
        if (!$statement) {
29
            // list logged user, information_schema.USER_PRIVILEGES lists just the current user too
30
            $statement = $this->driver->query("SELECT SUBSTRING_INDEX(CURRENT_USER, '@', 1) " .
31
                "AS User, SUBSTRING_INDEX(CURRENT_USER, '@', -1) AS Host");
32
        }
33
        $users = [];
34
        while ($row = $statement->fetchAssoc()) {
35
            $users[] = $row;
36
        }
37
        return $users;
38
    }
39
40
    /**
41
     * @param array $grants
42
     * @param array $row
43
     * @param string $password
44
     * 
45
     * @return void
46
     */
47
    private function setUserGrant(array &$grants, array $row, string &$password)
48
    {
49
        if (preg_match('~GRANT (.*) ON (.*) TO ~', $row[0], $match) &&
50
            preg_match_all('~ *([^(,]*[^ ,(])( *\([^)]+\))?~', $match[1], $matches, PREG_SET_ORDER)) { //! escape the part between ON and TO
51
            foreach ($matches as $val) {
52
                $match2 = $match[2] ?? '';
53
                $val2 = $val[2] ?? '';
54
                if ($val[1] != 'USAGE') {
55
                    $grants["$match2$val2"][$val[1]] = true;
56
                }
57
                if (preg_match('~ WITH GRANT OPTION~', $row[0])) { //! don't check inside strings and identifiers
58
                    $grants["$match2$val2"]['GRANT OPTION'] = true;
59
                }
60
            }
61
        }
62
        if (preg_match("~ IDENTIFIED BY PASSWORD '([^']+)~", $row[0], $match)) {
63
            $password = $match[1];
64
        }
65
    }
66
67
    /**
68
     * Get the grants of a user on a given host
69
     *
70
     * @param string $user      The username
71
     * @param string $host      The host name
72
     * @param string $password  The user password
73
     *
74
     * @return array
75
     */
76
    public function getUserGrants(string $user, string $host, string &$password): array
77
    {
78
        // From user.inc.php
79
        $grants = [];
80
81
        //! use information_schema for MySQL 5 - column names in column privileges are not escaped
82
        $query = 'SHOW GRANTS FOR ' . $this->driver->quote($user) . '@' . $this->driver->quote($host);
83
        if (($statement = $this->driver->query($query))) {
84
            while ($row = $statement->fetchRow()) {
85
                $this->setUserGrant($grants, $row, $password);
86
            }
87
        }
88
89
        return $grants;
90
    }
91
92
    /**
93
     * @param array $features
94
     * @param array $row
95
     *
96
     * @return void
97
     */
98
    private function makeFeatures(array &$features, array $row)
99
    {
100
        $contexts = explode(',', $row['Context']);
101
        foreach ($contexts as $context) {
102
            // Don't take 'Grant option' privileges.
103
            if ($row['Privilege'] === 'Grant option') {
104
                continue;
105
            }
106
            // Privileges of 'Server Admin' and 'File access on server' are merged
107
            if ($context === 'File access on server') {
108
                $context = 'Server Admin';
109
            }
110
            $privilege = $row['Privilege'];
111
            // Comment for this is 'No privileges - allow connect only'
112
            if ($context === 'Server Admin' && $privilege === 'Usage') {
113
                continue;
114
            }
115
            // MySQL bug #30305
116
            if ($context === 'Procedures' && $privilege === 'Create routine') {
117
                $context = 'Databases';
118
            }
119
            if (!isset($features[$context])) {
120
                $features[$context] = [];
121
            }
122
            $features[$context][$privilege] = $row['Comment'];
123
            if ($context === 'Tables' &&
124
                in_array($privilege, ['Select', 'Insert', 'Update', 'References'])) {
125
                $features['Columns'][$privilege] = $row['Comment'];
126
            }
127
        }
128
    }
129
130
    /**
131
     * @param string $privilege
132
     * @param string $desc
133
     * @param string $context
134
     * @param array $grants
135
     *
136
     * @return array
137
     */
138
    private function getUserPrivilegeDetail(string $privilege, string $desc, string $context, array $grants): array
139
    {
140
        $detail = [$desc, $this->util->html($privilege)];
141
        // echo '<tr><td' . ($desc ? ">$desc<td" : " colspan='2'") .
142
        //     ' lang="en" title="' . $this->util->html($comment) . '">' . $this->util->html($privilege);
143
        $i = 0;
144
        foreach ($grants as $object => $grant) {
145
            $name = "'grants[$i][" . $this->util->html(strtoupper($privilege)) . "]'";
146
            $value = $grant[strtoupper($privilege)] ?? false;
147
            if ($context == 'Server Admin' && $object != (isset($grants['*.*']) ? '*.*' : '.*')) {
148
                $detail[] = '';
149
            }
150
            // elseif(isset($values['grant']))
151
            // {
152
            //     $detail[] = "<select name=$name><option><option value='1'" .
153
            //         ($value ? ' selected' : '') . '>' . $this->trans->lang('Grant') .
154
            //         "<option value='0'" . ($value == '0' ? ' selected' : '') . '>' .
155
            //         $this->trans->lang('Revoke') . '</select>';
156
            // }
157
            else {
158
                $detail[] = "<input type='checkbox' name=$name" . ($value ? ' checked />' : ' />');
159
            }
160
            $i++;
161
        }
162
        return $detail;
163
    }
164
165
    /**
166
     * Get the user privileges
167
     *
168
     * @param array $grants     The user grants
169
     *
170
     * @return array
171
     */
172
    public function getUserPrivileges(array $grants): array
173
    {
174
        // From user.inc.php
175
        $features = [
176
            '' => [
177
                'All privileges' => '',
178
            ],
179
            'Columns' => [],
180
        ];
181
        $rows = $this->driver->rows('SHOW PRIVILEGES');
182
        foreach ($rows as $row) {
183
            $this->makeFeatures($features, $row);
184
        }
185
186
        foreach (array_keys($features['Tables']) as $privilege) {
187
            unset($features['Databases'][$privilege]);
188
        }
189
190
        $privileges = [];
191
        $contexts = [
192
            '' => '',
193
            'Server Admin' => $this->trans->lang('Server'),
194
            'Databases' => $this->trans->lang('Database'),
195
            'Tables' => $this->trans->lang('Table'),
196
            'Columns' => $this->trans->lang('Column'),
197
            'Procedures' => $this->trans->lang('Routine'),
198
        ];
199
        foreach ($contexts as $context => $desc) {
200
            foreach ($features[$context] as $privilege => $comment) {
201
                $privileges[] = $this->getUserPrivilegeDetail($privilege, $desc, $context, $grants);
202
            }
203
        }
204
205
        return $privileges;
206
    }
207
}
208