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