From 04d6d5ca99ebfd1cebb8ce06618fb3811fc1a8aa Mon Sep 17 00:00:00 2001 From: Charles Date: Thu, 9 Jan 2020 10:55:03 +0100 Subject: phpmyadmin working --- .../phpmyadmin/libraries/classes/Server/Plugin.php | 274 + .../libraries/classes/Server/Plugins.php | 74 + .../libraries/classes/Server/Privileges.php | 5649 ++++++++++++++++++++ .../phpmyadmin/libraries/classes/Server/Select.php | 128 + .../libraries/classes/Server/Status/Data.php | 430 ++ .../libraries/classes/Server/Status/Monitor.php | 546 ++ .../libraries/classes/Server/UserGroups.php | 390 ++ srcs/phpmyadmin/libraries/classes/Server/Users.php | 64 + 8 files changed, 7555 insertions(+) create mode 100644 srcs/phpmyadmin/libraries/classes/Server/Plugin.php create mode 100644 srcs/phpmyadmin/libraries/classes/Server/Plugins.php create mode 100644 srcs/phpmyadmin/libraries/classes/Server/Privileges.php create mode 100644 srcs/phpmyadmin/libraries/classes/Server/Select.php create mode 100644 srcs/phpmyadmin/libraries/classes/Server/Status/Data.php create mode 100644 srcs/phpmyadmin/libraries/classes/Server/Status/Monitor.php create mode 100644 srcs/phpmyadmin/libraries/classes/Server/UserGroups.php create mode 100644 srcs/phpmyadmin/libraries/classes/Server/Users.php (limited to 'srcs/phpmyadmin/libraries/classes/Server') diff --git a/srcs/phpmyadmin/libraries/classes/Server/Plugin.php b/srcs/phpmyadmin/libraries/classes/Server/Plugin.php new file mode 100644 index 0000000..9b45297 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Server/Plugin.php @@ -0,0 +1,274 @@ +name = $name; + $this->version = $version; + $this->status = $status; + $this->type = $type; + $this->typeVersion = $typeVersion; + $this->library = $library; + $this->libraryVersion = $libraryVersion; + $this->author = $author; + $this->description = $description; + $this->license = $license; + $this->loadOption = $loadOption; + $this->maturity = $maturity; + $this->authVersion = $authVersion; + } + + /** + * @param array $state array with the properties + * @return self + */ + public static function fromState(array $state): self + { + return new self( + $state['name'] ?? '', + $state['version'] ?? null, + $state['status'] ?? '', + $state['type'] ?? '', + $state['typeVersion'] ?? null, + $state['library'] ?? null, + $state['libraryVersion'] ?? null, + $state['author'] ?? null, + $state['description'] ?? null, + $state['license'] ?? '', + $state['loadOption'] ?? null, + $state['maturity'] ?? null, + $state['authVersion'] ?? null + ); + } + + /** + * @return array + */ + public function toArray(): array + { + return [ + 'name' => $this->getName(), + 'version' => $this->getVersion(), + 'status' => $this->getStatus(), + 'type' => $this->getType(), + 'type_version' => $this->getTypeVersion(), + 'library' => $this->getLibrary(), + 'library_version' => $this->getLibraryVersion(), + 'author' => $this->getAuthor(), + 'description' => $this->getDescription(), + 'license' => $this->getLicense(), + 'load_option' => $this->getLoadOption(), + 'maturity' => $this->getMaturity(), + 'auth_version' => $this->getAuthVersion(), + ]; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @return string|null + */ + public function getVersion(): ?string + { + return $this->version; + } + + /** + * @return string + */ + public function getStatus(): string + { + return $this->status; + } + + /** + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string|null + */ + public function getTypeVersion(): ?string + { + return $this->typeVersion; + } + + /** + * @return string|null + */ + public function getLibrary(): ?string + { + return $this->library; + } + + /** + * @return string|null + */ + public function getLibraryVersion(): ?string + { + return $this->libraryVersion; + } + + /** + * @return string|null + */ + public function getAuthor(): ?string + { + return $this->author; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @return string + */ + public function getLicense(): string + { + return $this->license; + } + + /** + * @return string|null + */ + public function getLoadOption(): ?string + { + return $this->loadOption; + } + + /** + * @return string|null + */ + public function getMaturity(): ?string + { + return $this->maturity; + } + + /** + * @return string|null + */ + public function getAuthVersion(): ?string + { + return $this->authVersion; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Server/Plugins.php b/srcs/phpmyadmin/libraries/classes/Server/Plugins.php new file mode 100644 index 0000000..eb8e85a --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Server/Plugins.php @@ -0,0 +1,74 @@ +dbi = $dbi; + } + + /** + * @return Plugin[] + */ + public function getAll(): array + { + global $cfg; + + $sql = 'SHOW PLUGINS'; + if (! $cfg['Server']['DisableIS']) { + $sql = 'SELECT * FROM information_schema.PLUGINS ORDER BY PLUGIN_TYPE, PLUGIN_NAME'; + } + $result = $this->dbi->query($sql); + $plugins = []; + while ($row = $this->dbi->fetchAssoc($result)) { + $plugins[] = $this->mapRowToPlugin($row); + } + $this->dbi->freeResult($result); + + return $plugins; + } + + /** + * @param array $row Row fetched from database + * @return Plugin + */ + private function mapRowToPlugin(array $row): Plugin + { + return Plugin::fromState([ + 'name' => $row['PLUGIN_NAME'] ?? $row['Name'], + 'version' => $row['PLUGIN_VERSION'] ?? null, + 'status' => $row['PLUGIN_STATUS'] ?? $row['Status'], + 'type' => $row['PLUGIN_TYPE'] ?? $row['Type'], + 'typeVersion' => $row['PLUGIN_TYPE_VERSION'] ?? null, + 'library' => $row['PLUGIN_LIBRARY'] ?? $row['Library'] ?? null, + 'libraryVersion' => $row['PLUGIN_LIBRARY_VERSION'] ?? null, + 'author' => $row['PLUGIN_AUTHOR'] ?? null, + 'description' => $row['PLUGIN_DESCRIPTION'] ?? null, + 'license' => $row['PLUGIN_LICENSE'] ?? $row['License'], + 'loadOption' => $row['LOAD_OPTION'] ?? null, + 'maturity' => $row['PLUGIN_MATURITY'] ?? null, + 'authVersion' => $row['PLUGIN_AUTH_VERSION'] ?? null, + ]); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Server/Privileges.php b/srcs/phpmyadmin/libraries/classes/Server/Privileges.php new file mode 100644 index 0000000..1e50fbb --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Server/Privileges.php @@ -0,0 +1,5649 @@ +template = $template; + $this->dbi = $dbi; + $this->relation = $relation; + $this->relationCleanup = $relationCleanup; + } + + /** + * Get Html for User Group Dialog + * + * @param string $username username + * @param bool $is_menuswork Is menuswork set in configuration + * + * @return string html + */ + public function getHtmlForUserGroupDialog($username, $is_menuswork) + { + $html = ''; + if (! empty($_GET['edit_user_group_dialog']) && $is_menuswork) { + $dialog = $this->getHtmlToChooseUserGroup($username); + $response = Response::getInstance(); + if ($response->isAjax()) { + $response->addJSON('message', $dialog); + exit; + } else { + $html .= $dialog; + } + } + + return $html; + } + + /** + * Escapes wildcard in a database+table specification + * before using it in a GRANT statement. + * + * Escaping a wildcard character in a GRANT is only accepted at the global + * or database level, not at table level; this is why I remove + * the escaping character. Internally, in mysql.tables_priv.Db there are + * no escaping (for example test_db) but in mysql.db you'll see test\_db + * for a db-specific privilege. + * + * @param string $dbname Database name + * @param string $tablename Table name + * + * @return string the escaped (if necessary) database.table + */ + public function wildcardEscapeForGrant($dbname, $tablename) + { + if (strlen($dbname) === 0) { + $db_and_table = '*.*'; + } else { + if (strlen($tablename) > 0) { + $db_and_table = Util::backquote( + Util::unescapeMysqlWildcards($dbname) + ) + . '.' . Util::backquote($tablename); + } else { + $db_and_table = Util::backquote($dbname) . '.*'; + } + } + return $db_and_table; + } + + /** + * Generates a condition on the user name + * + * @param string $initial the user's initial + * + * @return string the generated condition + */ + public function rangeOfUsers($initial = '') + { + // strtolower() is used because the User field + // might be BINARY, so LIKE would be case sensitive + if ($initial === null || $initial === '') { + return ''; + } + + $ret = " WHERE `User` LIKE '" + . $this->dbi->escapeString($initial) . "%'" + . " OR `User` LIKE '" + . $this->dbi->escapeString(mb_strtolower($initial)) + . "%'"; + return $ret; + } // end function + + /** + * Formats privilege name for a display + * + * @param array $privilege Privilege information + * @param boolean $html Whether to use HTML + * + * @return string + */ + public function formatPrivilege(array $privilege, $html) + { + if ($html) { + return '' + . $privilege[1] . ''; + } + + return $privilege[1]; + } + + /** + * Parses privileges into an array, it modifies the array + * + * @param array $row Results row from + * + * @return void + */ + public function fillInTablePrivileges(array &$row) + { + $row1 = $this->dbi->fetchSingleRow( + 'SHOW COLUMNS FROM `mysql`.`tables_priv` LIKE \'Table_priv\';', + 'ASSOC' + ); + // note: in MySQL 5.0.3 we get "Create View', 'Show view'; + // the View for Create is spelled with uppercase V + // the view for Show is spelled with lowercase v + // and there is a space between the words + + $av_grants = explode( + '\',\'', + mb_substr( + $row1['Type'], + mb_strpos($row1['Type'], '(') + 2, + mb_strpos($row1['Type'], ')') + - mb_strpos($row1['Type'], '(') - 3 + ) + ); + + $users_grants = explode(',', $row['Table_priv']); + + foreach ($av_grants as $current_grant) { + $row[$current_grant . '_priv'] + = in_array($current_grant, $users_grants) ? 'Y' : 'N'; + } + unset($row['Table_priv']); + } + + + /** + * Extracts the privilege information of a priv table row + * + * @param array|null $row the row + * @param boolean $enableHTML add tag with tooltips + * @param boolean $tablePrivs whether row contains table privileges + * + * @global resource $user_link the database connection + * + * @return array + */ + public function extractPrivInfo($row = null, $enableHTML = false, $tablePrivs = false) + { + if ($tablePrivs) { + $grants = $this->getTableGrantsArray(); + } else { + $grants = $this->getGrantsArray(); + } + + if ($row !== null && isset($row['Table_priv'])) { + $this->fillInTablePrivileges($row); + } + + $privs = []; + $allPrivileges = true; + foreach ($grants as $current_grant) { + if (($row !== null && isset($row[$current_grant[0]])) + || ($row === null && isset($GLOBALS[$current_grant[0]])) + ) { + if (($row !== null && $row[$current_grant[0]] == 'Y') + || ($row === null + && ($GLOBALS[$current_grant[0]] == 'Y' + || (is_array($GLOBALS[$current_grant[0]]) + && count($GLOBALS[$current_grant[0]]) == $_REQUEST['column_count'] + && empty($GLOBALS[$current_grant[0] . '_none'])))) + ) { + $privs[] = $this->formatPrivilege($current_grant, $enableHTML); + } elseif (! empty($GLOBALS[$current_grant[0]]) + && is_array($GLOBALS[$current_grant[0]]) + && empty($GLOBALS[$current_grant[0] . '_none']) + ) { + // Required for proper escaping of ` (backtick) in a column name + $grant_cols = array_map( + function ($val) { + return Util::backquote($val); + }, + $GLOBALS[$current_grant[0]] + ); + + $privs[] = $this->formatPrivilege($current_grant, $enableHTML) + . ' (' . implode(', ', $grant_cols) . ')'; + } else { + $allPrivileges = false; + } + } + } + if (empty($privs)) { + if ($enableHTML) { + $privs[] = 'USAGE'; + } else { + $privs[] = 'USAGE'; + } + } elseif ($allPrivileges + && (! isset($_POST['grant_count']) || count($privs) == $_POST['grant_count']) + ) { + if ($enableHTML) { + $privs = ['ALL PRIVILEGES', + ]; + } else { + $privs = ['ALL PRIVILEGES']; + } + } + return $privs; + } + + /** + * Returns an array of table grants and their descriptions + * + * @return array array of table grants + */ + public function getTableGrantsArray() + { + return [ + [ + 'Delete', + 'DELETE', + $GLOBALS['strPrivDescDelete'], + ], + [ + 'Create', + 'CREATE', + $GLOBALS['strPrivDescCreateTbl'], + ], + [ + 'Drop', + 'DROP', + $GLOBALS['strPrivDescDropTbl'], + ], + [ + 'Index', + 'INDEX', + $GLOBALS['strPrivDescIndex'], + ], + [ + 'Alter', + 'ALTER', + $GLOBALS['strPrivDescAlter'], + ], + [ + 'Create View', + 'CREATE_VIEW', + $GLOBALS['strPrivDescCreateView'], + ], + [ + 'Show view', + 'SHOW_VIEW', + $GLOBALS['strPrivDescShowView'], + ], + [ + 'Trigger', + 'TRIGGER', + $GLOBALS['strPrivDescTrigger'], + ], + ]; + } + + /** + * Get the grants array which contains all the privilege types + * and relevant grant messages + * + * @return array + */ + public function getGrantsArray() + { + return [ + [ + 'Select_priv', + 'SELECT', + __('Allows reading data.'), + ], + [ + 'Insert_priv', + 'INSERT', + __('Allows inserting and replacing data.'), + ], + [ + 'Update_priv', + 'UPDATE', + __('Allows changing data.'), + ], + [ + 'Delete_priv', + 'DELETE', + __('Allows deleting data.'), + ], + [ + 'Create_priv', + 'CREATE', + __('Allows creating new databases and tables.'), + ], + [ + 'Drop_priv', + 'DROP', + __('Allows dropping databases and tables.'), + ], + [ + 'Reload_priv', + 'RELOAD', + __('Allows reloading server settings and flushing the server\'s caches.'), + ], + [ + 'Shutdown_priv', + 'SHUTDOWN', + __('Allows shutting down the server.'), + ], + [ + 'Process_priv', + 'PROCESS', + __('Allows viewing processes of all users.'), + ], + [ + 'File_priv', + 'FILE', + __('Allows importing data from and exporting data into files.'), + ], + [ + 'References_priv', + 'REFERENCES', + __('Has no effect in this MySQL version.'), + ], + [ + 'Index_priv', + 'INDEX', + __('Allows creating and dropping indexes.'), + ], + [ + 'Alter_priv', + 'ALTER', + __('Allows altering the structure of existing tables.'), + ], + [ + 'Show_db_priv', + 'SHOW DATABASES', + __('Gives access to the complete list of databases.'), + ], + [ + 'Super_priv', + 'SUPER', + __( + 'Allows connecting, even if maximum number of connections ' + . 'is reached; required for most administrative operations ' + . 'like setting global variables or killing threads of other users.' + ), + ], + [ + 'Create_tmp_table_priv', + 'CREATE TEMPORARY TABLES', + __('Allows creating temporary tables.'), + ], + [ + 'Lock_tables_priv', + 'LOCK TABLES', + __('Allows locking tables for the current thread.'), + ], + [ + 'Repl_slave_priv', + 'REPLICATION SLAVE', + __('Needed for the replication slaves.'), + ], + [ + 'Repl_client_priv', + 'REPLICATION CLIENT', + __('Allows the user to ask where the slaves / masters are.'), + ], + [ + 'Create_view_priv', + 'CREATE VIEW', + __('Allows creating new views.'), + ], + [ + 'Event_priv', + 'EVENT', + __('Allows to set up events for the event scheduler.'), + ], + [ + 'Trigger_priv', + 'TRIGGER', + __('Allows creating and dropping triggers.'), + ], + // for table privs: + [ + 'Create View_priv', + 'CREATE VIEW', + __('Allows creating new views.'), + ], + [ + 'Show_view_priv', + 'SHOW VIEW', + __('Allows performing SHOW CREATE VIEW queries.'), + ], + // for table privs: + [ + 'Show view_priv', + 'SHOW VIEW', + __('Allows performing SHOW CREATE VIEW queries.'), + ], + [ + 'Delete_history_priv', + 'DELETE HISTORY', + $GLOBALS['strPrivDescDeleteHistoricalRows'], + ], + [ + 'Delete versioning rows_priv', + 'DELETE HISTORY', + $GLOBALS['strPrivDescDeleteHistoricalRows'], + ], + [ + 'Create_routine_priv', + 'CREATE ROUTINE', + __('Allows creating stored routines.'), + ], + [ + 'Alter_routine_priv', + 'ALTER ROUTINE', + __('Allows altering and dropping stored routines.'), + ], + [ + 'Create_user_priv', + 'CREATE USER', + __('Allows creating, dropping and renaming user accounts.'), + ], + [ + 'Execute_priv', + 'EXECUTE', + __('Allows executing stored routines.'), + ], + ]; + } + + /** + * Displays on which column(s) a table-specific privilege is granted + * + * @param array $columns columns array + * @param array $row first row from result or boolean false + * @param string $name_for_select privilege types - Select_priv, Insert_priv + * Update_priv, References_priv + * @param string $priv_for_header privilege for header + * @param string $name privilege name: insert, select, update, references + * @param string $name_for_dfn name for dfn + * @param string $name_for_current name for current + * + * @return string html snippet + */ + public function getHtmlForColumnPrivileges( + array $columns, + array $row, + $name_for_select, + $priv_for_header, + $name, + $name_for_dfn, + $name_for_current + ) { + return $this->template->render('server/privileges/column_privileges', [ + 'columns' => $columns, + 'row' => $row, + 'name_for_select' => $name_for_select, + 'priv_for_header' => $priv_for_header, + 'name' => $name, + 'name_for_dfn' => $name_for_dfn, + 'name_for_current' => $name_for_current, + ]); + } + + /** + * Get sql query for display privileges table + * + * @param string $db the database + * @param string $table the table + * @param string $username username for database connection + * @param string $hostname hostname for database connection + * + * @return string sql query + */ + public function getSqlQueryForDisplayPrivTable($db, $table, $username, $hostname) + { + if ($db == '*') { + return "SELECT * FROM `mysql`.`user`" + . " WHERE `User` = '" . $this->dbi->escapeString($username) . "'" + . " AND `Host` = '" . $this->dbi->escapeString($hostname) . "';"; + } elseif ($table == '*') { + return "SELECT * FROM `mysql`.`db`" + . " WHERE `User` = '" . $this->dbi->escapeString($username) . "'" + . " AND `Host` = '" . $this->dbi->escapeString($hostname) . "'" + . " AND '" . $this->dbi->escapeString(Util::unescapeMysqlWildcards($db)) . "'" + . " LIKE `Db`;"; + } + return "SELECT `Table_priv`" + . " FROM `mysql`.`tables_priv`" + . " WHERE `User` = '" . $this->dbi->escapeString($username) . "'" + . " AND `Host` = '" . $this->dbi->escapeString($hostname) . "'" + . " AND `Db` = '" . $this->dbi->escapeString(Util::unescapeMysqlWildcards($db)) . "'" + . " AND `Table_name` = '" . $this->dbi->escapeString($table) . "';"; + } + + /** + * Displays a dropdown to select the user group + * with menu items configured to each of them. + * + * @param string $username username + * + * @return string html to select the user group + */ + public function getHtmlToChooseUserGroup($username) + { + $cfgRelation = $this->relation->getRelationsParam(); + $groupTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['usergroups']); + $userTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['users']); + + $userGroup = ''; + if (isset($GLOBALS['username'])) { + $sql_query = "SELECT `usergroup` FROM " . $userTable + . " WHERE `username` = '" . $this->dbi->escapeString($username) . "'"; + $userGroup = $this->dbi->fetchValue( + $sql_query, + 0, + 0, + DatabaseInterface::CONNECT_CONTROL + ); + } + + $allUserGroups = ['' => '']; + $sql_query = "SELECT DISTINCT `usergroup` FROM " . $groupTable; + $result = $this->relation->queryAsControlUser($sql_query, false); + if ($result) { + while ($row = $this->dbi->fetchRow($result)) { + $allUserGroups[$row[0]] = $row[0]; + } + } + $this->dbi->freeResult($result); + + return $this->template->render('server/privileges/choose_user_group', [ + 'all_user_groups' => $allUserGroups, + 'user_group' => $userGroup, + 'params' => ['username' => $username], + ]); + } + + /** + * Sets the user group from request values + * + * @param string $username username + * @param string $userGroup user group to set + * + * @return void + */ + public function setUserGroup($username, $userGroup) + { + $userGroup = $userGroup === null ? '' : $userGroup; + $cfgRelation = $this->relation->getRelationsParam(); + if (empty($cfgRelation['db']) || empty($cfgRelation['users']) || empty($cfgRelation['usergroups'])) { + return; + } + + $userTable = Util::backquote($cfgRelation['db']) + . "." . Util::backquote($cfgRelation['users']); + + $sql_query = "SELECT `usergroup` FROM " . $userTable + . " WHERE `username` = '" . $this->dbi->escapeString($username) . "'"; + $oldUserGroup = $this->dbi->fetchValue( + $sql_query, + 0, + 0, + DatabaseInterface::CONNECT_CONTROL + ); + + if ($oldUserGroup === false) { + $upd_query = "INSERT INTO " . $userTable . "(`username`, `usergroup`)" + . " VALUES ('" . $this->dbi->escapeString($username) . "', " + . "'" . $this->dbi->escapeString($userGroup) . "')"; + } else { + if (empty($userGroup)) { + $upd_query = "DELETE FROM " . $userTable + . " WHERE `username`='" . $this->dbi->escapeString($username) . "'"; + } elseif ($oldUserGroup != $userGroup) { + $upd_query = "UPDATE " . $userTable + . " SET `usergroup`='" . $this->dbi->escapeString($userGroup) . "'" + . " WHERE `username`='" . $this->dbi->escapeString($username) . "'"; + } + } + if (isset($upd_query)) { + $this->relation->queryAsControlUser($upd_query); + } + } + + /** + * Displays the privileges form table + * + * @param string $db the database + * @param string $table the table + * @param boolean $submit whether to display the submit button or not + * + * @global array $cfg the phpMyAdmin configuration + * @global resource $user_link the database connection + * + * @return string html snippet + */ + public function getHtmlToDisplayPrivilegesTable( + $db = '*', + $table = '*', + $submit = true + ) { + $html_output = ''; + $sql_query = ''; + + if ($db == '*') { + $table = '*'; + } + $username = ''; + $hostname = ''; + if (isset($GLOBALS['username'])) { + $username = $GLOBALS['username']; + $hostname = $GLOBALS['hostname']; + $sql_query = $this->getSqlQueryForDisplayPrivTable( + $db, + $table, + $username, + $hostname + ); + $row = $this->dbi->fetchSingleRow($sql_query); + } + if (empty($row)) { + if ($table == '*' && $this->dbi->isSuperuser()) { + $row = []; + if ($db == '*') { + $sql_query = 'SHOW COLUMNS FROM `mysql`.`user`;'; + } elseif ($table == '*') { + $sql_query = 'SHOW COLUMNS FROM `mysql`.`db`;'; + } + $res = $this->dbi->query($sql_query); + while ($row1 = $this->dbi->fetchRow($res)) { + if (mb_substr($row1[0], 0, 4) == 'max_') { + $row[$row1[0]] = 0; + } elseif (mb_substr($row1[0], 0, 5) == 'x509_' + || mb_substr($row1[0], 0, 4) == 'ssl_' + ) { + $row[$row1[0]] = ''; + } else { + $row[$row1[0]] = 'N'; + } + } + $this->dbi->freeResult($res); + } elseif ($table == '*') { + $row = []; + } else { + $row = ['Table_priv' => '']; + } + } + if (isset($row['Table_priv'])) { + $this->fillInTablePrivileges($row); + + // get columns + $res = $this->dbi->tryQuery( + 'SHOW COLUMNS FROM ' + . Util::backquote( + Util::unescapeMysqlWildcards($db) + ) + . '.' . Util::backquote($table) . ';' + ); + $columns = []; + if ($res) { + while ($row1 = $this->dbi->fetchRow($res)) { + $columns[$row1[0]] = [ + 'Select' => false, + 'Insert' => false, + 'Update' => false, + 'References' => false, + ]; + } + $this->dbi->freeResult($res); + } + unset($res, $row1); + } + // table-specific privileges + if (! empty($columns)) { + $html_output .= $this->getHtmlForTableSpecificPrivileges( + $username, + $hostname, + $db, + $table, + $columns, + $row + ); + } else { + // global or db-specific + $html_output .= $this->getHtmlForGlobalOrDbSpecificPrivs($db, $table, $row); + } + $html_output .= '' . "\n"; + if ($submit) { + $html_output .= '' . "\n"; + } + return $html_output; + } // end of the 'PMA_displayPrivTable()' function + + /** + * Get HTML for "Require" + * + * @param array $row privilege array + * + * @return string html snippet + */ + public function getHtmlForRequires(array $row) + { + $specified = (isset($row['ssl_type']) && $row['ssl_type'] == 'SPECIFIED'); + $require_options = [ + [ + 'name' => 'ssl_type', + 'value' => 'NONE', + 'description' => __( + 'Does not require SSL-encrypted connections.' + ), + 'label' => 'REQUIRE NONE', + 'checked' => isset($row['ssl_type']) + && ($row['ssl_type'] == 'NONE' + || $row['ssl_type'] == '') + ? 'checked="checked"' + : '', + 'disabled' => false, + 'radio' => true, + ], + [ + 'name' => 'ssl_type', + 'value' => 'ANY', + 'description' => __( + 'Requires SSL-encrypted connections.' + ), + 'label' => 'REQUIRE SSL', + 'checked' => isset($row['ssl_type']) && ($row['ssl_type'] == 'ANY') + ? 'checked="checked"' + : '', + 'disabled' => false, + 'radio' => true, + ], + [ + 'name' => 'ssl_type', + 'value' => 'X509', + 'description' => __( + 'Requires a valid X509 certificate.' + ), + 'label' => 'REQUIRE X509', + 'checked' => isset($row['ssl_type']) && ($row['ssl_type'] == 'X509') + ? 'checked="checked"' + : '', + 'disabled' => false, + 'radio' => true, + ], + [ + 'name' => 'ssl_type', + 'value' => 'SPECIFIED', + 'description' => '', + 'label' => 'SPECIFIED', + 'checked' => $specified ? 'checked="checked"' : '', + 'disabled' => false, + 'radio' => true, + ], + [ + 'name' => 'ssl_cipher', + 'value' => isset($row['ssl_cipher']) + ? htmlspecialchars($row['ssl_cipher']) : '', + 'description' => __( + 'Requires that a specific cipher method be used for a connection.' + ), + 'label' => 'REQUIRE CIPHER', + 'checked' => '', + 'disabled' => ! $specified, + 'radio' => false, + ], + [ + 'name' => 'x509_issuer', + 'value' => isset($row['x509_issuer']) + ? htmlspecialchars($row['x509_issuer']) : '', + 'description' => __( + 'Requires that a valid X509 certificate issued by this CA be presented.' + ), + 'label' => 'REQUIRE ISSUER', + 'checked' => '', + 'disabled' => ! $specified, + 'radio' => false, + ], + [ + 'name' => 'x509_subject', + 'value' => isset($row['x509_subject']) + ? htmlspecialchars($row['x509_subject']) : '', + 'description' => __( + 'Requires that a valid X509 certificate with this subject be presented.' + ), + 'label' => 'REQUIRE SUBJECT', + 'checked' => '', + 'disabled' => ! $specified, + 'radio' => false, + ], + ]; + + return $this->template->render('server/privileges/require_options', [ + 'require_options' => $require_options, + ]); + } + + /** + * Get HTML for "Resource limits" + * + * @param array $row first row from result or boolean false + * + * @return string html snippet + */ + public function getHtmlForResourceLimits(array $row) + { + $limits = [ + [ + 'input_name' => 'max_questions', + 'name_main' => 'MAX QUERIES PER HOUR', + 'value' => isset($row['max_questions']) ? $row['max_questions'] : '0', + 'description' => __( + 'Limits the number of queries the user may send to the server per hour.' + ), + ], + [ + 'input_name' => 'max_updates', + 'name_main' => 'MAX UPDATES PER HOUR', + 'value' => isset($row['max_updates']) ? $row['max_updates'] : '0', + 'description' => __( + 'Limits the number of commands that change any table ' + . 'or database the user may execute per hour.' + ), + ], + [ + 'input_name' => 'max_connections', + 'name_main' => 'MAX CONNECTIONS PER HOUR', + 'value' => isset($row['max_connections']) ? $row['max_connections'] : '0', + 'description' => __( + 'Limits the number of new connections the user may open per hour.' + ), + ], + [ + 'input_name' => 'max_user_connections', + 'name_main' => 'MAX USER_CONNECTIONS', + 'value' => isset($row['max_user_connections']) ? + $row['max_user_connections'] : '0', + 'description' => __( + 'Limits the number of simultaneous connections ' + . 'the user may have.' + ), + ], + ]; + + return $this->template->render('server/privileges/resource_limits', [ + 'limits' => $limits, + ]); + } + + /** + * Get the HTML snippet for routine specific privileges + * + * @param string $username username for database connection + * @param string $hostname hostname for database connection + * @param string $db the database + * @param string $routine the routine + * @param string $url_dbname url encoded db name + * + * @return string + */ + public function getHtmlForRoutineSpecificPrivileges( + $username, + $hostname, + $db, + $routine, + $url_dbname + ) { + $header = $this->getHtmlHeaderForUserProperties( + false, + $url_dbname, + $db, + $username, + $hostname, + $routine, + 'routine' + ); + + $sql = "SELECT `Proc_priv`" + . " FROM `mysql`.`procs_priv`" + . " WHERE `User` = '" . $this->dbi->escapeString($username) . "'" + . " AND `Host` = '" . $this->dbi->escapeString($hostname) . "'" + . " AND `Db` = '" + . $this->dbi->escapeString(Util::unescapeMysqlWildcards($db)) . "'" + . " AND `Routine_name` LIKE '" . $this->dbi->escapeString($routine) . "';"; + $res = $this->dbi->fetchValue($sql); + + $privs = $this->parseProcPriv($res); + + $routineArray = [$this->getTriggerPrivilegeTable()]; + $privTableNames = [__('Routine')]; + $privCheckboxes = $this->getHtmlForGlobalPrivTableWithCheckboxes( + $routineArray, + $privTableNames, + $privs + ); + + return $this->template->render('server/privileges/edit_routine_privileges', [ + 'username' => $username, + 'hostname' => $hostname, + 'database' => $db, + 'routine' => $routine, + 'grant_count' => count($privs), + 'priv_checkboxes' => $privCheckboxes, + 'header' => $header, + ]); + } + + /** + * Get routine privilege table as an array + * + * @return array privilege type array + */ + public function getTriggerPrivilegeTable() + { + $routinePrivTable = [ + [ + 'Grant', + 'GRANT', + __( + 'Allows user to give to other users or remove from other users ' + . 'privileges that user possess on this routine.' + ), + ], + [ + 'Alter_routine', + 'ALTER ROUTINE', + __('Allows altering and dropping this routine.'), + ], + [ + 'Execute', + 'EXECUTE', + __('Allows executing this routine.'), + ], + ]; + return $routinePrivTable; + } + + /** + * Get the HTML snippet for table specific privileges + * + * @param string $username username for database connection + * @param string $hostname hostname for database connection + * @param string $db the database + * @param string $table the table + * @param array $columns columns array + * @param array $row current privileges row + * + * @return string + */ + public function getHtmlForTableSpecificPrivileges( + $username, + $hostname, + $db, + $table, + array $columns, + array $row + ) { + $res = $this->dbi->query( + 'SELECT `Column_name`, `Column_priv`' + . ' FROM `mysql`.`columns_priv`' + . ' WHERE `User`' + . ' = \'' . $this->dbi->escapeString($username) . "'" + . ' AND `Host`' + . ' = \'' . $this->dbi->escapeString($hostname) . "'" + . ' AND `Db`' + . ' = \'' . $this->dbi->escapeString( + Util::unescapeMysqlWildcards($db) + ) . "'" + . ' AND `Table_name`' + . ' = \'' . $this->dbi->escapeString($table) . '\';' + ); + + while ($row1 = $this->dbi->fetchRow($res)) { + $row1[1] = explode(',', $row1[1]); + foreach ($row1[1] as $current) { + $columns[$row1[0]][$current] = true; + } + } + $this->dbi->freeResult($res); + unset($res, $row1, $current); + + $html_output = '' . "\n" + . '' . "\n" + . '
' . "\n" + . '' . __('Table-specific privileges') + . '' + . '

' + . __('Note: MySQL privilege names are expressed in English.') + . '

'; + + // privs that are attached to a specific column + $html_output .= $this->getHtmlForAttachedPrivilegesToTableSpecificColumn( + $columns, + $row + ); + + // privs that are not attached to a specific column + $html_output .= '
' . "\n" + . $this->getHtmlForNotAttachedPrivilegesToTableSpecificColumn($row) + . '
' . "\n"; + + // for Safari 2.0.2 + $html_output .= '
' . "\n"; + + return $html_output; + } + + /** + * Get HTML snippet for privileges that are attached to a specific column + * + * @param array $columns columns array + * @param array $row first row from result or boolean false + * + * @return string + */ + public function getHtmlForAttachedPrivilegesToTableSpecificColumn(array $columns, array $row) + { + $html_output = $this->getHtmlForColumnPrivileges( + $columns, + $row, + 'Select_priv', + 'SELECT', + 'select', + __('Allows reading data.'), + 'Select' + ); + + $html_output .= $this->getHtmlForColumnPrivileges( + $columns, + $row, + 'Insert_priv', + 'INSERT', + 'insert', + __('Allows inserting and replacing data.'), + 'Insert' + ); + + $html_output .= $this->getHtmlForColumnPrivileges( + $columns, + $row, + 'Update_priv', + 'UPDATE', + 'update', + __('Allows changing data.'), + 'Update' + ); + + $html_output .= $this->getHtmlForColumnPrivileges( + $columns, + $row, + 'References_priv', + 'REFERENCES', + 'references', + __('Has no effect in this MySQL version.'), + 'References' + ); + return $html_output; + } + + /** + * Get HTML for privileges that are not attached to a specific column + * + * @param array $row first row from result or boolean false + * + * @return string + */ + public function getHtmlForNotAttachedPrivilegesToTableSpecificColumn(array $row) + { + $html_output = ''; + + foreach ($row as $current_grant => $current_grant_value) { + $grant_type = substr($current_grant, 0, -5); + if (in_array($grant_type, ['Select', 'Insert', 'Update', 'References']) + ) { + continue; + } + // make a substitution to match the messages variables; + // also we must substitute the grant we get, because we can't generate + // a form variable containing blanks (those would get changed to + // an underscore when receiving the POST) + if ($current_grant == 'Create View_priv') { + $tmp_current_grant = 'CreateView_priv'; + $current_grant = 'Create_view_priv'; + } elseif ($current_grant == 'Show view_priv') { + $tmp_current_grant = 'ShowView_priv'; + $current_grant = 'Show_view_priv'; + } elseif ($current_grant == 'Delete versioning rows_priv') { + $tmp_current_grant = 'DeleteHistoricalRows_priv'; + $current_grant = 'Delete_history_priv'; + } else { + $tmp_current_grant = $current_grant; + } + + $html_output .= '
' . "\n" + . '' . "\n"; + + $privGlobalName1 = 'strPrivDesc' + . mb_substr( + $tmp_current_grant, + 0, + - 5 + ); + $html_output .= '' . "\n" + . '
' . "\n"; + } // end foreach () + return $html_output; + } + + /** + * Get HTML for global or database specific privileges + * + * @param string $db the database + * @param string $table the table + * @param array $row first row from result or boolean false + * + * @return string + */ + public function getHtmlForGlobalOrDbSpecificPrivs($db, $table, array $row) + { + $privTable_names = [ + 0 => __('Data'), + 1 => __('Structure'), + 2 => __('Administration'), + ]; + $privTable = []; + $privTable[0] = $this->getDataPrivilegeTable($db); + $privTable[1] = $this->getStructurePrivilegeTable($table, $row); + $privTable[2] = $this->getAdministrationPrivilegeTable($db); + + $html_output = ''; + if ($db == '*') { + $legend = __('Global privileges'); + $menu_label = __('Global'); + } elseif ($table == '*') { + $legend = __('Database-specific privileges'); + $menu_label = __('Database'); + } else { + $legend = __('Table-specific privileges'); + $menu_label = __('Table'); + } + $html_output .= '
' + . '' . $legend + . ' ' + . ' ' + . '' + . '

' + . __('Note: MySQL privilege names are expressed in English.') + . '

'; + + // Output the Global privilege tables with checkboxes + $html_output .= $this->getHtmlForGlobalPrivTableWithCheckboxes( + $privTable, + $privTable_names, + $row + ); + + // The "Resource limits" box is not displayed for db-specific privs + if ($db == '*') { + $html_output .= $this->getHtmlForResourceLimits($row); + $html_output .= $this->getHtmlForRequires($row); + } + // for Safari 2.0.2 + $html_output .= '
'; + + return $html_output; + } + + /** + * Get data privilege table as an array + * + * @param string $db the database + * + * @return array data privilege table + */ + public function getDataPrivilegeTable($db) + { + $data_privTable = [ + [ + 'Select', + 'SELECT', + __('Allows reading data.'), + ], + [ + 'Insert', + 'INSERT', + __('Allows inserting and replacing data.'), + ], + [ + 'Update', + 'UPDATE', + __('Allows changing data.'), + ], + [ + 'Delete', + 'DELETE', + __('Allows deleting data.'), + ], + ]; + if ($db == '*') { + $data_privTable[] + = [ + 'File', + 'FILE', + __('Allows importing data from and exporting data into files.'), + ]; + } + return $data_privTable; + } + + /** + * Get structure privilege table as an array + * + * @param string $table the table + * @param array $row first row from result or boolean false + * + * @return array structure privilege table + */ + public function getStructurePrivilegeTable($table, array $row) + { + $structure_privTable = [ + [ + 'Create', + 'CREATE', + $table == '*' + ? __('Allows creating new databases and tables.') + : __('Allows creating new tables.'), + ], + [ + 'Alter', + 'ALTER', + __('Allows altering the structure of existing tables.'), + ], + [ + 'Index', + 'INDEX', + __('Allows creating and dropping indexes.'), + ], + [ + 'Drop', + 'DROP', + $table == '*' + ? __('Allows dropping databases and tables.') + : __('Allows dropping tables.'), + ], + [ + 'Create_tmp_table', + 'CREATE TEMPORARY TABLES', + __('Allows creating temporary tables.'), + ], + [ + 'Show_view', + 'SHOW VIEW', + __('Allows performing SHOW CREATE VIEW queries.'), + ], + [ + 'Create_routine', + 'CREATE ROUTINE', + __('Allows creating stored routines.'), + ], + [ + 'Alter_routine', + 'ALTER ROUTINE', + __('Allows altering and dropping stored routines.'), + ], + [ + 'Execute', + 'EXECUTE', + __('Allows executing stored routines.'), + ], + ]; + // this one is for a db-specific priv: Create_view_priv + if (isset($row['Create_view_priv'])) { + $structure_privTable[] = [ + 'Create_view', + 'CREATE VIEW', + __('Allows creating new views.'), + ]; + } + // this one is for a table-specific priv: Create View_priv + if (isset($row['Create View_priv'])) { + $structure_privTable[] = [ + 'Create View', + 'CREATE VIEW', + __('Allows creating new views.'), + ]; + } + if (isset($row['Event_priv'])) { + // MySQL 5.1.6 + $structure_privTable[] = [ + 'Event', + 'EVENT', + __('Allows to set up events for the event scheduler.'), + ]; + $structure_privTable[] = [ + 'Trigger', + 'TRIGGER', + __('Allows creating and dropping triggers.'), + ]; + } + return $structure_privTable; + } + + /** + * Get administration privilege table as an array + * + * @param string $db the table + * + * @return array administration privilege table + */ + public function getAdministrationPrivilegeTable($db) + { + if ($db == '*') { + $adminPrivTable = [ + [ + 'Grant', + 'GRANT', + __( + 'Allows adding users and privileges ' + . 'without reloading the privilege tables.' + ), + ], + ]; + $adminPrivTable[] = [ + 'Super', + 'SUPER', + __( + 'Allows connecting, even if maximum number ' + . 'of connections is reached; required for ' + . 'most administrative operations like ' + . 'setting global variables or killing threads of other users.' + ), + ]; + $adminPrivTable[] = [ + 'Process', + 'PROCESS', + __('Allows viewing processes of all users.'), + ]; + $adminPrivTable[] = [ + 'Reload', + 'RELOAD', + __('Allows reloading server settings and flushing the server\'s caches.'), + ]; + $adminPrivTable[] = [ + 'Shutdown', + 'SHUTDOWN', + __('Allows shutting down the server.'), + ]; + $adminPrivTable[] = [ + 'Show_db', + 'SHOW DATABASES', + __('Gives access to the complete list of databases.'), + ]; + } else { + $adminPrivTable = [ + [ + 'Grant', + 'GRANT', + __( + 'Allows user to give to other users or remove from other' + . ' users the privileges that user possess yourself.' + ), + ], + ]; + } + $adminPrivTable[] = [ + 'Lock_tables', + 'LOCK TABLES', + __('Allows locking tables for the current thread.'), + ]; + $adminPrivTable[] = [ + 'References', + 'REFERENCES', + __('Has no effect in this MySQL version.'), + ]; + if ($db == '*') { + $adminPrivTable[] = [ + 'Repl_client', + 'REPLICATION CLIENT', + __('Allows the user to ask where the slaves / masters are.'), + ]; + $adminPrivTable[] = [ + 'Repl_slave', + 'REPLICATION SLAVE', + __('Needed for the replication slaves.'), + ]; + $adminPrivTable[] = [ + 'Create_user', + 'CREATE USER', + __('Allows creating, dropping and renaming user accounts.'), + ]; + } + return $adminPrivTable; + } + + /** + * Get HTML snippet for global privileges table with check boxes + * + * @param array $privTable privileges table array + * @param array $privTableNames names of the privilege tables + * (Data, Structure, Administration) + * @param array $row first row from result or boolean false + * + * @return string + */ + public function getHtmlForGlobalPrivTableWithCheckboxes( + array $privTable, + array $privTableNames, + array $row + ) { + return $this->template->render('server/privileges/global_priv_table', [ + 'priv_table' => $privTable, + 'priv_table_names' => $privTableNames, + 'row' => $row, + ]); + } + + /** + * Gets the currently active authentication plugins + * + * @param string $orig_auth_plugin Default Authentication plugin + * @param string $mode are we creating a new user or are we just + * changing one? + * (allowed values: 'new', 'edit', 'change_pw') + * @param string $versions Is MySQL version newer or older than 5.5.7 + * + * @return string + */ + public function getHtmlForAuthPluginsDropdown( + $orig_auth_plugin, + $mode = 'new', + $versions = 'new' + ) { + $select_id = 'select_authentication_plugin' + . ($mode == 'change_pw' ? '_cp' : ''); + + if ($versions == 'new') { + $active_auth_plugins = $this->getActiveAuthPlugins(); + + if (isset($active_auth_plugins['mysql_old_password'])) { + unset($active_auth_plugins['mysql_old_password']); + } + } else { + $active_auth_plugins = [ + 'mysql_native_password' => __('Native MySQL authentication'), + ]; + } + + $html_output = Util::getDropdown( + 'authentication_plugin', + $active_auth_plugins, + $orig_auth_plugin, + $select_id + ); + + return $html_output; + } + + /** + * Gets the currently active authentication plugins + * + * @return array array of plugin names and descriptions + */ + public function getActiveAuthPlugins() + { + $get_plugins_query = "SELECT `PLUGIN_NAME`, `PLUGIN_DESCRIPTION`" + . " FROM `information_schema`.`PLUGINS` " + . "WHERE `PLUGIN_TYPE` = 'AUTHENTICATION';"; + $resultset = $this->dbi->query($get_plugins_query); + + $r