diff --git a/ChangeLog b/ChangeLog index 4cb58ed89d..b162bc360d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -21,6 +21,8 @@ phpMyAdmin - ChangeLog + rfe #1490 Dynamic process list + rfe #1522 Drag and Drop SQL import + rfe #637 Custom Field Handlers ++ rfe #1488 User privilege tab not shown in all relevant cases ++ rfe #781 Privileges for non superuser 4.2.7.0 (not yet released) - bug Broken links on home page diff --git a/js/server_privileges.js b/js/server_privileges.js index c77cc521c7..b6587ad936 100644 --- a/js/server_privileges.js +++ b/js/server_privileges.js @@ -41,8 +41,10 @@ function checkAddUser(the_form) */ function appendNewUser(new_user_string, new_user_initial, new_user_initial_string) { + if (!$('#usersForm').length) { + return; + } //Append the newly retrieved user to the table now - //Calculate the index for the new row var $curr_last_row = $("#usersForm").find('tbody').find('tr:last'); var $curr_first_row = $("#usersForm").find('tbody').find('tr:first'); diff --git a/libraries/DatabaseInterface.class.php b/libraries/DatabaseInterface.class.php index af23846166..91accfecf2 100644 --- a/libraries/DatabaseInterface.class.php +++ b/libraries/DatabaseInterface.class.php @@ -1834,40 +1834,90 @@ class PMA_DatabaseInterface } /** - * Checks if current user is superuser while caching - * the result in session. + * gets the current user with host * - * @return bool Whether use is a superuser + * @return string the current user i.e. user@host + */ + public function getCurrentUser() + { + if (PMA_Util::cacheExists('mysql_cur_user')) { + return PMA_Util::cacheGet('mysql_cur_user'); + } + $user = $GLOBALS['dbi']->fetchValue('SELECT USER();'); + if ($user !== false) { + PMA_Util::cacheSet('mysql_cur_user', $user); + return PMA_Util::cacheGet('mysql_cur_user'); + } + return ''; + } + + /** + * Checks if current user is superuser + * + * @return bool Whether user is a superuser */ public function isSuperuser() { - if (PMA_Util::cacheExists('is_superuser')) { - return PMA_Util::cacheGet('is_superuser'); + return self::isUserType('super'); + } + + /** + * Checks if current user has global create user/grant privilege + * or is a superuser (i.e. SELECT on mysql.users) + * while caching the result in session. + * + * @param string $type type of user to check for + * i.e. 'create', 'grant', 'super' + * + * @return bool Whether user is a given type of user + */ + public function isUserType($type) + { + if (PMA_Util::cacheExists('is_' . $type . 'user')) { + return PMA_Util::cacheGet('is_' . $type . 'user'); + } + + // Prepare query for each user type check + $query = ''; + if ($type === 'super') { + $query = 'SELECT 1 FROM mysql.user LIMIT 1'; + } elseif ($type === 'create') { + $query = 'SELECT 1 FROM INFORMATION_SCHEMA.USER_PRIVILEGES ' + . 'WHERE PRIVILEGE_TYPE = \'CREATE USER\' LIMIT 1'; + } elseif ($type === 'grant') { + $query = 'SELECT 1 FROM INFORMATION_SCHEMA.USER_PRIVILEGES ' + . 'WHERE IS_GRANTABLE = \'YES\' LIMIT 1'; } // when connection failed we don't have a $userlink if (isset($GLOBALS['userlink'])) { + $is = false; if (PMA_DRIZZLE) { // Drizzle has no authorization by default, so when no plugin is // enabled everyone is a superuser // Known authorization libraries: regex_policy, simple_user_policy // Plugins limit object visibility (dbs, tables, processes), we can // safely assume we always deal with superuser - $result = true; + $is = true; } else { - // check access to mysql.user table - $result = (bool) $GLOBALS['dbi']->tryQuery( - 'SELECT COUNT(*) FROM mysql.user', + // Check information_schema.user_privileges table + // for global create user rights + $result = $GLOBALS['dbi']->tryQuery( + $query, $GLOBALS['userlink'], self::QUERY_STORE ); + if ($result) { + $is = (bool) $GLOBALS['dbi']->numRows($result); + } + $GLOBALS['dbi']->freeResult($result); } - PMA_Util::cacheSet('is_superuser', $result); + PMA_Util::cacheSet('is_' . $type . 'user', $is); } else { - PMA_Util::cacheSet('is_superuser', false); + PMA_Util::cacheSet('is_' . $type . 'user', false); } - return PMA_Util::cacheGet('is_superuser'); + return PMA_Util::cacheGet('is_' . $type . 'user'); } /** diff --git a/libraries/Menu.class.php b/libraries/Menu.class.php index 148ab6c491..98f0de7da8 100644 --- a/libraries/Menu.class.php +++ b/libraries/Menu.class.php @@ -288,6 +288,8 @@ class PMA_Menu $db_is_system_schema = $GLOBALS['dbi']->isSystemSchema($this->_db); $tbl_is_view = PMA_Table::isView($this->_db, $this->_table); $is_superuser = $GLOBALS['dbi']->isSuperuser(); + $isCreateOrGrantUser = $GLOBALS['dbi']->isUserType('grant') + || $GLOBALS['dbi']->isUserType('create'); $tabs = array(); @@ -331,7 +333,9 @@ class PMA_Menu $tabs['import']['link'] = 'tbl_import.php'; $tabs['import']['text'] = __('Import'); } - if ($is_superuser && ! PMA_DRIZZLE && ! $db_is_system_schema) { + if (($is_superuser || $isCreateOrGrantUser) + && ! PMA_DRIZZLE && ! $db_is_system_schema + ) { $tabs['privileges']['link'] = 'server_privileges.php'; $tabs['privileges']['args']['checkprivsdb'] = $this->_db; $tabs['privileges']['args']['checkprivstable'] = $this->_table; @@ -389,6 +393,8 @@ class PMA_Menu $db_is_system_schema = $GLOBALS['dbi']->isSystemSchema($this->_db); $num_tables = count($GLOBALS['dbi']->getTables($this->_db)); $is_superuser = $GLOBALS['dbi']->isSuperuser(); + $isCreateOrGrantUser = $GLOBALS['dbi']->isUserType('grant') + || $GLOBALS['dbi']->isUserType('create'); /** * Gets the relation settings @@ -435,7 +441,7 @@ class PMA_Menu $tabs['operation']['text'] = __('Operations'); $tabs['operation']['icon'] = 'b_tblops.png'; - if ($is_superuser && ! PMA_DRIZZLE) { + if (($is_superuser || $isCreateOrGrantUser) && ! PMA_DRIZZLE) { $tabs['privileges']['link'] = 'server_privileges.php'; $tabs['privileges']['args']['checkprivsdb'] = $this->_db; // stay on database view @@ -492,6 +498,8 @@ class PMA_Menu private function _getServerTabs() { $is_superuser = isset($GLOBALS['dbi']) && $GLOBALS['dbi']->isSuperuser(); + $isCreateOrGrantUser = $GLOBALS['dbi']->isUserType('grant') + || $GLOBALS['dbi']->isUserType('create'); $binary_logs = null; $notDrizzle = ! defined('PMA_DRIZZLE') || (defined('PMA_DRIZZLE') && ! PMA_DRIZZLE); @@ -529,7 +537,7 @@ class PMA_Menu ) ); - if ($is_superuser && ! PMA_DRIZZLE) { + if (($is_superuser || $isCreateOrGrantUser) && ! PMA_DRIZZLE) { $tabs['rights']['icon'] = 's_rights.png'; $tabs['rights']['link'] = 'server_privileges.php'; $tabs['rights']['text'] = __('Users'); diff --git a/libraries/Util.class.php b/libraries/Util.class.php index 5ac90734b1..5559a67e57 100644 --- a/libraries/Util.class.php +++ b/libraries/Util.class.php @@ -2796,6 +2796,8 @@ class PMA_Util public static function clearUserCache() { self::cacheUnset('is_superuser'); + self::cacheUnset('is_createuser'); + self::cacheUnset('is_grantuser'); } /** diff --git a/libraries/dbi/DBIDummy.class.php b/libraries/dbi/DBIDummy.class.php index f43d8ca198..4e456e3574 100644 --- a/libraries/dbi/DBIDummy.class.php +++ b/libraries/dbi/DBIDummy.class.php @@ -26,8 +26,18 @@ $GLOBALS['dummy_queries'] = array( 'result' => array(array('pma_test@localhost')), ), array( - 'query' => 'SELECT COUNT(*) FROM mysql.user', - 'result' => false, + 'query' => 'SELECT 1 FROM mysql.user LIMIT 1', + 'result' => array(array('1')), + ), + array( + 'query' => 'SELECT 1 FROM INFORMATION_SCHEMA.USER_PRIVILEGES ' + . 'WHERE PRIVILEGE_TYPE = \'CREATE USER\' LIMIT 1', + 'result' => array(array('1')), + ), + array( + 'query' => 'SELECT 1 FROM INFORMATION_SCHEMA.USER_PRIVILEGES ' + . 'WHERE IS_GRANTABLE = \'YES\' LIMIT 1', + 'result' => array(array('1')), ), array( 'query' => 'SHOW MASTER LOGS', diff --git a/libraries/server_common.inc.php b/libraries/server_common.inc.php index 4b1eb47398..b6372a65c6 100644 --- a/libraries/server_common.inc.php +++ b/libraries/server_common.inc.php @@ -32,10 +32,12 @@ $err_url = 'index.php' . $GLOBALS['url_query']; /** * @global boolean Checks for superuser privileges */ -$is_superuser = $GLOBALS['dbi']->isSuperuser(); +$GLOBALS['is_superuser'] = $GLOBALS['dbi']->isSuperuser(); +$GLOBALS['is_grantuser'] = $GLOBALS['dbi']->isUserType('grant'); +$GLOBALS['is_createuser'] = $GLOBALS['dbi']->isUserType('create'); // now, select the mysql db -if ($is_superuser && ! PMA_DRIZZLE) { +if ($GLOBALS['is_superuser'] && ! PMA_DRIZZLE) { $GLOBALS['dbi']->selectDb('mysql', $GLOBALS['userlink']); } diff --git a/libraries/server_privileges.lib.php b/libraries/server_privileges.lib.php index 1d63556f91..e2cc2f8eec 100644 --- a/libraries/server_privileges.lib.php +++ b/libraries/server_privileges.lib.php @@ -633,7 +633,7 @@ function PMA_getHtmlToDisplayPrivilegesTable($db = '*', $row = $GLOBALS['dbi']->fetchSingleRow($sql_query); } if (empty($row)) { - if ($table == '*') { + if ($table == '*' && $GLOBALS['is_superuser']) { if ($db == '*') { $sql_query = 'SHOW COLUMNS FROM `mysql`.`user`;'; } elseif ($table == '*') { @@ -648,6 +648,8 @@ function PMA_getHtmlToDisplayPrivilegesTable($db = '*', } } $GLOBALS['dbi']->freeResult($res); + } elseif ($table == '*') { + $row = array(); } else { $row = array('Table_priv' => ''); } @@ -722,7 +724,9 @@ function PMA_getHtmlForResourceLimits($row) . 'MAX QUERIES PER HOUR' . '' . "\n" . '' . "\n" @@ -1227,7 +1237,8 @@ function PMA_getHtmlForGlobalPrivTableWithCheckboxes( . ' name="' . $priv[0] . '_priv" ' . 'id="checkbox_' . $priv[0] . '_priv"' . ' value="Y" title="' . $priv[2] . '"' - . (($row[$priv[0] . '_priv'] == 'Y') + . ((isset($row[$priv[0] . '_priv']) + && $row[$priv[0] . '_priv'] == 'Y') ? ' checked="checked"' : '' ) @@ -1747,7 +1758,9 @@ function PMA_getHtmlForAddUser($dbname) } $html_output .= '' . "\n"; - $html_output .= PMA_getHtmlToDisplayPrivilegesTable('*', '*', false); + if ($GLOBALS['is_grantuser']) { + $html_output .= PMA_getHtmlToDisplayPrivilegesTable('*', '*', false); + } $html_output .= '' - . '' . "\n"; - if ($GLOBALS['is_ajax_request'] == true && empty($_REQUEST['ajax_page_request']) ) { @@ -1897,23 +1876,7 @@ function PMA_getHtmlForSpecificDbPrivileges($db) exit; } else { // Offer to create a new user for the current database - $html_output .= '
' . "\n" - . '' . _pgettext('Create new user', 'New') . '' . "\n"; - - $html_output .= '' . "\n" - . PMA_Util::getIcon('b_usradd.png') - . ' ' . __('Add user') . '' . "\n"; - - $html_output .= '
' . "\n"; + $html_output .= PMA_getAddUserHtmlFieldset($db); } return $html_output; } @@ -1928,36 +1891,60 @@ function PMA_getHtmlForSpecificDbPrivileges($db) */ function PMA_getHtmlForSpecificTablePrivileges($db, $table) { - // check the privileges for a particular table. - $html_output = '
'; - $html_output .= '
'; - $html_output .= '' - . PMA_Util::getIcon('b_usrcheck.png') - . sprintf( - __('Users having access to "%s"'), - '' - . htmlspecialchars($db) . '.' . htmlspecialchars($table) - . '' - ) - . ''; + $html_output = ''; + if ($GLOBALS['is_superuser']) { + // check the privileges for a particular table. + $html_output = ''; + $html_output .= '
'; + $html_output .= '' + . PMA_Util::getIcon('b_usrcheck.png') + . sprintf( + __('Users having access to "%s"'), + '' + . htmlspecialchars($db) . '.' . htmlspecialchars($table) + . '' + ) + . ''; - $html_output .= ''; - $html_output .= '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . ''; + $html_output .= '
' . __('User') . '' . __('Host') . '' . __('Type') . '' . __('Privileges') . '' . __('Grant') . '' . __('Action') . '
'; + $html_output .= PMA_getHtmlForPrivsTableHead(); + $privMap = PMA_getPrivMap($db); + $sql_query = "SELECT `User`, `Host`, `Db`," + . " 't' AS `Type`, `Table_name`, `Table_priv`" + . " FROM `mysql`.`tables_priv`" + . " WHERE '" . PMA_Util::sqlAddSlashes($db) . "' LIKE `Db`" + . " AND '" . PMA_Util::sqlAddSlashes($table) . "' LIKE `Table_name`" + . " AND NOT (`Table_priv` = '' AND Column_priv = '')" + . " ORDER BY `User` ASC, `Host` ASC, `Db` ASC, `Table_priv` ASC;"; + $res = $GLOBALS['dbi']->query($sql_query); + PMA_mergePrivMapFromResult($privMap, $res); + $html_output .= PMA_getHtmlTableBodyForSpecificDbOrTablePrivs($privMap, $db); + $html_output .= '
'; + $html_output .= '
'; + $html_output .= ''; + } else { + $html_output .= PMA_getHtmlForViewUsersError(); + } + // Offer to create a new user for the current database + $html_output .= PMA_getAddUserHtmlFieldset($db, $table); + return $html_output; +} +/** + * gets privilege map + * + * @param string $db the database + * + * @return array $privMap the privilege map + */ +function PMA_getPrivMap($db) +{ list($listOfPrivs, $listOfComparedPrivs) = PMA_getListOfPrivilegesAndComparedPrivileges(); $sql_query @@ -1975,9 +1962,22 @@ function PMA_getHtmlForSpecificTablePrivileges($db, $table) . ")" . " ORDER BY `User` ASC, `Host` ASC, `Db` ASC;"; $res = $GLOBALS['dbi']->query($sql_query); - $privMap = array(); - while ($row = $GLOBALS['dbi']->fetchAssoc($res)) { + PMA_mergePrivMapFromResult($privMap, $res); + return $privMap; +} + +/** + * merge privilege map and rows from resultset + * + * @param array &$privMap the privilege map reference + * @param object $result the resultset of query + * + * @return void + */ +function PMA_mergePrivMapFromResult(&$privMap, $result) +{ + while ($row = $GLOBALS['dbi']->fetchAssoc($result)) { $user = $row['User']; $host = $row['Host']; if (! isset($privMap[$user])) { @@ -1988,59 +1988,44 @@ function PMA_getHtmlForSpecificTablePrivileges($db, $table) } $privMap[$user][$host][] = $row; } +} - $sql_query = "SELECT `User`, `Host`, `Db`," - . " 't' AS `Type`, `Table_name`, `Table_priv`" - . " FROM `mysql`.`tables_priv`" - . " WHERE '" . PMA_Util::sqlAddSlashes($db) . "' LIKE `Db`" - . " AND '" . PMA_Util::sqlAddSlashes($table) . "' LIKE `Table_name`" - . " AND NOT (`Table_priv` = '' AND Column_priv = '')" - . " ORDER BY `User` ASC, `Host` ASC, `Db` ASC, `Table_priv` ASC;"; - $res = $GLOBALS['dbi']->query($sql_query); +/** + * Get HTML snippet for privileges table head + * + * @return string $html_output + */ +function PMA_getHtmlForPrivsTableHead() +{ + return '' + . '' . __('User') . '' + . '' . __('Host') . '' + . '' . __('Type') . '' + . '' . __('Privileges') . '' + . '' . __('Grant') . '' + . '' . __('Action') . '' + . '' + . ''; +} - while ($row = $GLOBALS['dbi']->fetchAssoc($res)) { - $user = $row['User']; - $host = $row['Host']; - if (! isset($privMap[$user])) { - $privMap[$user] = array(); - } - if (! isset($privMap[$user][$host])) { - $privMap[$user][$host] = array(); - } - $privMap[$user][$host][] = $row; - } - - $html_output .= PMA_getHtmlTableBodyForSpecificDbOrTablePrivs($privMap, $db); - $html_output .= ''; - $html_output .= '
'; - $html_output .= ''; - - // Offer to create a new user for the current database - $html_output .= '
' - . '' . _pgettext('Create new user', 'New') . ''; - $html_output .= '' - . PMA_Util::getIcon('b_usradd.png') . __('Add user') . ''; - - $html_output .= '
'; - return $html_output; +/** + * Get HTML error for View Users form + * For non superusers such as grant/create users + * + * @return string $html_output + */ +function PMA_getHtmlForViewUsersError() +{ + return PMA_Message::error( + __('Not enough privilege to view users.') + )->getDisplay(); } /** * Get HTML snippet for table body of specific database or table privileges * - * @param array $privMap priviledge map - * @param boolean $db database + * @param array $privMap priviledge map + * @param string $db database * * @return string $html_output */ @@ -3118,14 +3103,7 @@ function PMA_getHtmlTableBodyForUserRights($db_rights) */ function PMA_getFieldsetForAddDeleteUser() { - $html_output = '
' . "\n"; - $html_output .= '' . "\n" - . PMA_Util::getIcon('b_usradd.png') - . ' ' . __('Add user') . '' . "\n"; - $html_output .= '
' . "\n"; - + $html_output = PMA_getAddUserHtmlFieldset(); $html_output .= '
' . '' . "\n" . PMA_Util::getIcon('b_usrdrop.png') @@ -3768,14 +3746,39 @@ function PMA_getListForExportUserDefinition($username, $hostname) /** * Get HTML for display Add userfieldset * + * @param string $db the database + * @param string $table the table name + * * @return string html output */ -function PMA_getAddUserHtmlFieldset() +function PMA_getAddUserHtmlFieldset($db = '', $table = '') { + if (!$GLOBALS['is_createuser']) { + return ''; + } + $rel_params = array(); + $url_params = array( + 'adduser' => 1 + ); + if (!empty($db)) { + $url_params['dbname'] + = $rel_params['checkprivsdb'] + = $db; + } + if (!empty($table)) { + $url_params['tablename'] + = $rel_params['checkprivstable'] + = $table; + } + return '
' . "\n" + . '' . _pgettext('Create new user', 'New') . '' . '' . "\n" + . PMA_URL_getCommon($url_params) . '" ' + . (!empty($rel_params) + ? ('rel="' . PMA_URL_getCommon($rel_params) . '" ') + : '') + . 'class="ajax">' . "\n" . PMA_Util::getIcon('b_usradd.png') . ' ' . __('Add user') . '' . "\n" . '
' . "\n"; @@ -3851,7 +3854,18 @@ function PMA_getHtmlHeaderForUserProperties( } $html_output .= '' . "\n"; - + $cur_user = htmlspecialchars($GLOBALS['dbi']->getCurrentUser()); + $user = htmlspecialchars($username . '@' . $hostname); + // Add a short notice for the user + // to remind him that he is editing his own privileges + if ($user === $cur_user) { + $html_output .= PMA_Message::notice( + __( + 'Note: You are attempting to edit privileges of the ' + . 'user with which you are currently logged in.' + ) + )->getDisplay(); + } return $html_output; } @@ -3897,15 +3911,16 @@ function PMA_getHtmlForUserOverview($pmaThemeImage, $text_dir) // - the privilege tables use a structure of an earlier version. // so let's try a more simple query + $GLOBALS['dbi']->freeResult($res); + $GLOBALS['dbi']->freeResult($res_all); $sql_query = 'SELECT * FROM `mysql`.`user`'; $res = $GLOBALS['dbi']->tryQuery( $sql_query, null, PMA_DatabaseInterface::QUERY_STORE ); if (! $res) { - $html_output .= PMA_Message::error(__('No Privileges'))->getDisplay(); - $GLOBALS['dbi']->freeResult($res); - unset($res); + $html_output .= PMA_getHtmlForViewUsersError(); + $html_output .= PMA_getAddUserHtmlFieldset(); } else { // This message is hardcoded because I will replace it by // a automatic repair feature soon. @@ -3917,6 +3932,7 @@ function PMA_getHtmlForUserOverview($pmaThemeImage, $text_dir) . ' to solve this problem!'; $html_output .= PMA_Message::rawError($raw)->getDisplay(); } + $GLOBALS['dbi']->freeResult($res); } else { $db_rights = PMA_getDbRightsForUserOverview(); // for all initials, even non A-Z @@ -4218,9 +4234,11 @@ function PMA_getDbSpecificPrivsQueriesForChangeOrCopyUser( function PMA_addUserAndCreateDatabase($_error, $real_sql_query, $sql_query, $username, $hostname, $dbname ) { - if ($_error || ! $GLOBALS['dbi']->tryQuery($real_sql_query)) { + if ($_error || (!empty($real_sql_query) + && !$GLOBALS['dbi']->tryQuery($real_sql_query)) + ) { $_REQUEST['createdb-1'] = $_REQUEST['createdb-2'] - = $_REQUEST['createdb-3'] = false; + = $_REQUEST['createdb-3'] = null; $message = PMA_Message::rawError($GLOBALS['dbi']->getError()); } else { $message = PMA_Message::success(__('You have added a new user.')); @@ -4308,9 +4326,14 @@ function PMA_getSqlQueriesForDisplayAndAddUser($username, $hostname, $password) . PMA_Util::sqlAddSlashes($hostname) . '\''; if ($_POST['pred_password'] != 'none' && $_POST['pred_password'] != 'keep') { - $sql_query = $real_sql_query . ' IDENTIFIED BY \'***\''; - $real_sql_query .= ' IDENTIFIED BY \'' - . PMA_Util::sqlAddSlashes($_POST['pma_pw']) . '\''; + $sql_query = $real_sql_query; + // Requires SELECT privilege on mysql database + // for using this with GRANT queries. It can be skipped. + if ($GLOBALS['is_superuser']) { + $sql_query .= ' IDENTIFIED BY \'***\''; + $real_sql_query .= ' IDENTIFIED BY \'' + . PMA_Util::sqlAddSlashes($_POST['pma_pw']) . '\''; + } if (isset($create_user_real)) { $create_user_show = $create_user_real . ' IDENTIFIED BY \'***\''; $create_user_real .= ' IDENTIFIED BY \'' @@ -4344,6 +4367,11 @@ function PMA_getSqlQueriesForDisplayAndAddUser($username, $hostname, $password) } $real_sql_query .= ';'; $sql_query .= ';'; + // No Global GRANT_OPTION privilege + if (!$GLOBALS['is_grantuser']) { + $real_sql_query = ''; + $sql_query = ''; + } return array($create_user_real, $create_user_show, diff --git a/libraries/server_users.lib.php b/libraries/server_users.lib.php index 57d59b7657..4a7d02d5f6 100644 --- a/libraries/server_users.lib.php +++ b/libraries/server_users.lib.php @@ -25,13 +25,16 @@ function PMA_getHtmlForSubMenusOnUsersPage($selfUrl) 'name' => __('Users overview'), 'url' => 'server_privileges.php', 'specific_params' => '&viewing_mode=server' - ), - array( + ) + ); + + if ($GLOBALS['is_superuser']) { + $items[] = array( 'name' => __('User groups'), 'url' => 'server_user_groups.php', 'specific_params' => '' - ) - ); + ); + } $retval = '