diff --git a/.gitignore b/.gitignore index cdb4db7e12..5c34723c13 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,4 @@ composer.lock phpstan.neon libraries/cache/ js/dist/ +/setup/styles.css.map diff --git a/ChangeLog b/ChangeLog index e0f1699e4c..cd0e8847c0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -214,6 +214,23 @@ phpMyAdmin - ChangeLog - issue #15677 Fix show process-list triggers a php exception - issue #15697 Fix uncaught php error: "Call to a member function get() on null" in db_export.php when exporting a table from the list +4.9.11 (2023-02-07) +- issue [security] Fix an XSS attack through the drag-and-drop upload feature + +4.9.10 (2022-02-10) +- issue #17308 Fix broken pagination links in the navigation sidebar + +4.9.9 (2022-01-22) +- issue #17305 Fix syntax error for PHP 5 +- issue #17307 Fix hide_connection_errors being undefined when a controluser is set + +4.9.8 (2022-01-20) +- issue #14321 Display a correct error page when "$cfg['Servers'][$i]['SignonURL']" is empty for auth_type=signon +- issue #14321 [security] Remove leaked HTML on signon page redirect before login for auth_type=signon +- issue [security] Add configuration directive $cfg['Servers'][$i]['hide_connection_errors'] to allow hiding host names and other error details when login fails +- issue [security] Add configuration directive $cfg['URLQueryEncryption'] to allow encrypting senstive information in the URL +- issue [security] Fix a scenario where an authenticated user can disable two factor authentication + 4.9.7 (2020-10-15) - issue #16397 Fix compatibility problems with older PHP versions (also issue #16399) - issue #16396 Fix broken two-factor authentication diff --git a/composer.json b/composer.json index 6716ab9842..fcb3d414ae 100644 --- a/composer.json +++ b/composer.json @@ -68,7 +68,7 @@ "samyoul/u2f-php-server": "<1.1" }, "suggest": { - "ext-openssl": "Cookie encryption", + "ext-openssl": "For encryption performance", "ext-curl": "Updates checking", "ext-opcache": "Better performance", "ext-zlib": "For gz import and export", @@ -78,7 +78,10 @@ "ext-mbstring": "For best performance", "tecnickcom/tcpdf": "For PDF support", "pragmarx/google2fa-qrcode": "For 2FA authentication", - "samyoul/u2f-php-server": "For FIDO U2F authentication" + "pragmarx/google2fa": "For 2FA authentication", + "bacon/bacon-qr-code": "For 2FA authentication", + "samyoul/u2f-php-server": "For FIDO U2F authentication", + "paragonie/sodium_compat": "For modern encryption support" }, "require-dev": { "php-webdriver/webdriver": "^1.7.1", @@ -88,7 +91,9 @@ "pragmarx/google2fa-qrcode": "^1.0.1", "samyoul/u2f-php-server": "^1.1", "squizlabs/php_codesniffer": "^3.0", - "tecnickcom/tcpdf": "^6.3" + "tecnickcom/tcpdf": "^6.3", + "paragonie/random_compat": ">=2", + "paragonie/sodium_compat": "^1.17" }, "extra": { "branch-alias": { @@ -107,6 +112,11 @@ ] }, "config":{ - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "phpstan/extension-installer": true, + "composer/package-versions-deprecated": true + } } } diff --git a/config.sample.inc.php b/config.sample.inc.php index c4f4df6f80..85587c61d4 100644 --- a/config.sample.inc.php +++ b/config.sample.inc.php @@ -149,6 +149,13 @@ $cfg['SaveDir'] = ''; */ //$cfg['SendErrorReports'] = 'always'; +/** + * 'URLQueryEncryption' defines whether phpMyAdmin will encrypt sensitive data from the URL query string. + * 'URLQueryEncryptionSecretKey' is a 32 bytes long secret key used to encrypt/decrypt the URL query string. + */ +//$cfg['URLQueryEncryption'] = true; +//$cfg['URLQueryEncryptionSecretKey'] = ''; + /** * You can find more configuration options in the documentation * in the doc/ folder or at . diff --git a/doc/config.rst b/doc/config.rst index a74bd222f1..fbf60bdd41 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -1485,6 +1485,20 @@ Server connection settings after logout (doesn't affect config authentication method). Should be absolute including protocol. +.. config:option:: $cfg['Servers'][$i]['hide_connection_errors'] + + :type: boolean + :default: false + + .. versionadded:: 4.9.8 + + Whether to show or hide detailed MySQL/MariaDB connection errors on the login page. + + .. note:: + + This error message can contain the target database server hostname or IP address, + which may reveal information about your network to an attacker. + Generic settings ---------------- @@ -1753,6 +1767,27 @@ Generic settings When enabled, a user can drag a file in to their browser and phpMyAdmin will attempt to import the file. +.. config:option:: $cfg['URLQueryEncryption'] + + :type: boolean + :default: false + + .. versionadded:: 4.9.8 + + Define whether phpMyAdmin will encrypt sensitive data (like database name + and table name) from the URL query string. Default is to not encrypt the URL + query string. + +.. config:option:: $cfg['URLQueryEncryptionSecretKey'] + + :type: string + :default: ``''`` + + .. versionadded:: 4.9.8 + + A secret key used to encrypt/decrypt the URL query string. + Should be 32 bytes long. + Cookie authentication options ----------------------------- diff --git a/js/drag_drop_import.js b/js/drag_drop_import.js index 8780f38ed8..2f25dea05c 100644 --- a/js/drag_drop_import.js +++ b/js/drag_drop_import.js @@ -132,7 +132,7 @@ var DragDropImport = { var filename = $this.parent('span').attr('data-filename'); $('body').append('

' + Messages.dropImportImportResultHeader + ' - ' + - filename + 'x

' + value.message + '
'); + Functions.escapeHtml(filename) + 'x' + value.message + ''); $('.pma_drop_result').draggable(); // to make this dialog draggable } }); diff --git a/js/navigation.js b/js/navigation.js index b48214ea50..c5200f21b1 100644 --- a/js/navigation.js +++ b/js/navigation.js @@ -161,7 +161,7 @@ Navigation.loadChildNodes = function (isNode, $expandElem, callback) { } var url = $('#pma_navigation').find('a.navigation_url').attr('href'); - $.get(url, params, function (data) { + $.post(url, params, function (data) { if (typeof data !== 'undefined' && data.success === true) { $destination.find('div.list_container').remove(); // FIXME: Hack, there shouldn't be a list container there if (isNode) { @@ -1070,14 +1070,12 @@ Navigation.selectCurrentDatabase = function () { Navigation.treePagination = function ($this) { var $msgbox = Functions.ajaxShowMessage(); var isDbSelector = $this.closest('div.pageselector').is('.dbselector'); - var url; - var params; + var url = 'navigation.php'; + var params = 'ajax_request=true'; if ($this[0].tagName === 'A') { - url = $this.attr('href'); - params = 'ajax_request=true'; + params += PMA_commonParams.get('arg_separator') + $this.getPostData(); } else { // tagName === 'SELECT' - url = 'navigation.php'; - params = $this.closest('form').serialize() + CommonParams.get('arg_separator') + 'ajax_request=true'; + params += PMA_commonParams.get('arg_separator') + $this.closest('form').serialize(); } var searchClause = Navigation.FastFilter.getSearchClause(); if (searchClause) { diff --git a/libraries/classes/Controllers/Table/StructureController.php b/libraries/classes/Controllers/Table/StructureController.php index 20d74bf011..8aaa658a69 100644 --- a/libraries/classes/Controllers/Table/StructureController.php +++ b/libraries/classes/Controllers/Table/StructureController.php @@ -342,7 +342,10 @@ class StructureController extends AbstractController // got to be eliminated in long run $db = &$this->db; $table = &$this->table; - $url_params = []; + $url_params = [ + 'db' => $db, + 'table' => $table, + ]; include_once ROOT_PATH . 'libraries/tbl_common.inc.php'; $this->_db_is_system_schema = $db_is_system_schema; $this->_url_query = Url::getCommonRaw([ @@ -1280,14 +1283,6 @@ class StructureController extends AbstractController 'DistinctValues' => Util::getIcon('b_browse', __('Distinct values')), ]; - $edit_view_url = ''; - if ($this->_tbl_is_view && ! $this->_db_is_system_schema) { - $edit_view_url = Url::getCommon([ - 'db' => $this->db, - 'table' => $this->table, - ]); - } - /** * Displays Space usage and row statistics */ @@ -1359,10 +1354,6 @@ class StructureController extends AbstractController $engine = $this->table_obj->getStorageEngine(); return $this->template->render('table/structure/display_structure', [ - 'url_params' => [ - 'db' => $this->db, - 'table' => $this->table, - ], 'collations' => $collations, 'is_foreign_key_supported' => Util::isForeignKeySupported($engine), 'displayIndexesHtml' => Index::getHtmlForDisplayIndexes(), @@ -1374,11 +1365,11 @@ class StructureController extends AbstractController 'tbl_is_view' => $this->_tbl_is_view, 'mime_map' => $mime_map, 'url_query' => $this->_url_query, + 'url_params' => $url_params, 'titles' => $titles, 'tbl_storage_engine' => $this->_tbl_storage_engine, 'primary' => $primary_index, 'columns_with_unique_index' => $columns_with_unique_index, - 'edit_view_url' => $edit_view_url, 'columns_list' => $columns_list, 'table_stats' => isset($tablestats) ? $tablestats : null, 'fields' => $fields, diff --git a/libraries/classes/Core.php b/libraries/classes/Core.php index a6b791b6a6..b2175426c0 100644 --- a/libraries/classes/Core.php +++ b/libraries/classes/Core.php @@ -1304,4 +1304,36 @@ class Core $hmac = hash_hmac('sha256', $sqlQuery, $secret . $cfg['blowfish_secret']); return hash_equals($hmac, $signature); } + + /** + * @return void + */ + public static function populateRequestWithEncryptedQueryParams() + { + if ( + (! isset($_GET['eq']) || ! is_string($_GET['eq'])) + && (! isset($_POST['eq']) || ! is_string($_POST['eq'])) + ) { + unset($_GET['eq'], $_POST['eq'], $_REQUEST['eq']); + return; + } + + $isFromPost = isset($_POST['eq']); + $decryptedQuery = Url::decryptQuery($isFromPost ? $_POST['eq'] : $_GET['eq']); + unset($_GET['eq'], $_POST['eq'], $_REQUEST['eq']); + if ($decryptedQuery === null) { + return; + } + + $urlQueryParams = (array) json_decode($decryptedQuery); + foreach ($urlQueryParams as $urlQueryParamKey => $urlQueryParamValue) { + if ($isFromPost) { + $_POST[$urlQueryParamKey] = $urlQueryParamValue; + } else { + $_GET[$urlQueryParamKey] = $urlQueryParamValue; + } + + $_REQUEST[$urlQueryParamKey] = $urlQueryParamValue; + } + } } diff --git a/libraries/classes/Crypto/Crypto.php b/libraries/classes/Crypto/Crypto.php new file mode 100644 index 0000000000..f0ef18ec5d --- /dev/null +++ b/libraries/classes/Crypto/Crypto.php @@ -0,0 +1,161 @@ +hasRandomBytesSupport = ! $forceFallback && is_callable('random_bytes'); + $this->hasSodiumSupport = ! $forceFallback + && $this->hasRandomBytesSupport + && is_callable('sodium_crypto_secretbox') + && is_callable('sodium_crypto_secretbox_open') + && defined('SODIUM_CRYPTO_SECRETBOX_NONCEBYTES') + && defined('SODIUM_CRYPTO_SECRETBOX_KEYBYTES'); + } + + /** + * @param string $plaintext + * + * @return string + */ + public function encrypt($plaintext) + { + if ($this->hasSodiumSupport) { + return $this->encryptWithSodium($plaintext); + } + + return $this->encryptWithPhpseclib($plaintext); + } + + /** + * @param string $ciphertext + * + * @return string + */ + public function decrypt($ciphertext) + { + if ($this->hasSodiumSupport) { + return $this->decryptWithSodium($ciphertext); + } + + return $this->decryptWithPhpseclib($ciphertext); + } + + /** + * @return string + */ + private function getEncryptionKey() + { + global $PMA_Config; + + $keyLength = $this->hasSodiumSupport ? SODIUM_CRYPTO_SECRETBOX_KEYBYTES : 32; + + $key = $PMA_Config->get('URLQueryEncryptionSecretKey'); + if (is_string($key) && mb_strlen($key, '8bit') === $keyLength) { + return $key; + } + + $key = isset($_SESSION['URLQueryEncryptionSecretKey']) ? $_SESSION['URLQueryEncryptionSecretKey'] : null; + if (is_string($key) && mb_strlen($key, '8bit') === $keyLength) { + return $key; + } + + $key = $this->hasRandomBytesSupport ? random_bytes($keyLength) : Random::string($keyLength); + $_SESSION['URLQueryEncryptionSecretKey'] = $key; + + return $key; + } + + /** + * @param string $plaintext + * + * @return string + */ + private function encryptWithPhpseclib($plaintext) + { + $key = $this->getEncryptionKey(); + $cipher = new AES(AES::MODE_CBC); + $iv = $this->hasRandomBytesSupport ? random_bytes(16) : Random::string(16); + $cipher->setIV($iv); + $cipher->setKey($key); + $ciphertext = $cipher->encrypt($plaintext); + $hmac = hash_hmac('sha256', $iv . $ciphertext, $key, true); + + return $hmac . $iv . $ciphertext; + } + + /** + * @param string $encrypted + * + * @return string|null + */ + private function decryptWithPhpseclib($encrypted) + { + $key = $this->getEncryptionKey(); + $hmac = mb_substr($encrypted, 0, 32, '8bit'); + $iv = mb_substr($encrypted, 32, 16, '8bit'); + $ciphertext = mb_substr($encrypted, 48, null, '8bit'); + $calculatedHmac = hash_hmac('sha256', $iv . $ciphertext, $key, true); + if (! hash_equals($hmac, $calculatedHmac)) { + return null; + } + + $cipher = new AES(AES::MODE_CBC); + $cipher->setIV($iv); + $cipher->setKey($key); + + return $cipher->decrypt($ciphertext); + } + + /** + * @param string $plaintext + * + * @return string + */ + private function encryptWithSodium($plaintext) + { + $key = $this->getEncryptionKey(); + $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); + $ciphertext = sodium_crypto_secretbox($plaintext, $nonce, $key); + + return $nonce . $ciphertext; + } + + /** + * @param string $encrypted + * + * @return string|null + */ + private function decryptWithSodium($encrypted) + { + $key = $this->getEncryptionKey(); + $nonce = mb_substr($encrypted, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit'); + $ciphertext = mb_substr($encrypted, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit'); + try { + $decrypted = sodium_crypto_secretbox_open($ciphertext, $nonce, $key); + } catch (Exception $e) { + return null; + } + + if ($decrypted === false) { + return null; + } + + return $decrypted; + } +} diff --git a/libraries/classes/DatabaseInterface.php b/libraries/classes/DatabaseInterface.php index 179285852d..9feaa8200e 100644 --- a/libraries/classes/DatabaseInterface.php +++ b/libraries/classes/DatabaseInterface.php @@ -1630,17 +1630,22 @@ class DatabaseInterface */ public function initRelationParamsCache() { - if (strlen($GLOBALS['db'])) { - $cfgRelation = $this->relation->getRelationsParam(); - if (empty($cfgRelation['db'])) { - $this->relation->fixPmaTables($GLOBALS['db'], false); - } - } - $cfgRelation = $this->relation->getRelationsParam(); - if (empty($cfgRelation['db']) && isset($GLOBALS['dblist'])) { - if ($GLOBALS['dblist']->databases->exists('phpmyadmin')) { - $this->relation->fixPmaTables('phpmyadmin', false); - } + $storageDbName = isset($GLOBALS['cfg']['Server']['pmadb']) ? $GLOBALS['cfg']['Server']['pmadb'] : ''; + // Use "phpmyadmin" as a default database name to check to keep the behavior consistent + $storageDbName = $storageDbName !== null + && is_string($storageDbName) + && $storageDbName !== '' ? $storageDbName : 'phpmyadmin'; + + // This will make users not having explicitly listed databases + // have config values filled by the default phpMyAdmin storage table name values + $this->relation->fixPmaTables($storageDbName, false); + + // This global will be changed if fixPmaTables did find one valid table + $storageDbName = isset($GLOBALS['cfg']['Server']['pmadb']) ? $GLOBALS['cfg']['Server']['pmadb'] : ''; + + // Empty means that until now no pmadb was found eligible + if (empty($storageDbName)) { + $this->relation->fixPmaTables($GLOBALS['db'], false); } } @@ -2522,6 +2527,8 @@ class DatabaseInterface $server = []; + $server['hide_connection_errors'] = $cfg['Server']['hide_connection_errors']; + if (! empty($cfg['Server']['controlhost'])) { $server['host'] = $cfg['Server']['controlhost']; } else { @@ -2592,6 +2599,10 @@ class DatabaseInterface $server['compress'] = false; } + if (! isset($server['hide_connection_errors'])) { + $server['hide_connection_errors'] = false; + } + return [ $user, $password, diff --git a/libraries/classes/Dbi/DbiMysqli.php b/libraries/classes/Dbi/DbiMysqli.php index 8663da1d3d..c74be0f9e5 100644 --- a/libraries/classes/Dbi/DbiMysqli.php +++ b/libraries/classes/Dbi/DbiMysqli.php @@ -121,15 +121,27 @@ class DbiMysqli implements DbiExtension $host = $server['host']; } - $return_value = $mysqli->real_connect( - $host, - $user, - $password, - '', - $server['port'], - (string) $server['socket'], - $client_flags - ); + if ($server['hide_connection_errors']) { + $return_value = @$mysqli->real_connect( + $host, + $user, + $password, + '', + $server['port'], + (string) $server['socket'], + $client_flags + ); + } else { + $return_value = $mysqli->real_connect( + $host, + $user, + $password, + '', + $server['port'], + (string) $server['socket'], + $client_flags + ); + } if ($return_value === false || $return_value === null) { /* @@ -151,7 +163,20 @@ class DbiMysqli implements DbiExtension ); $server['ssl'] = true; return self::connect($user, $password, $server); + } elseif ($error_number === 1045 && $server['hide_connection_errors']) { + trigger_error( + sprintf( + __( + 'Error 1045: Access denied for user. Additional error information' + . ' may be available, but is being hidden by the %s configuration directive.' + ), + '[code][doc@cfg_Servers_hide_connection_errors]' + . '$cfg[\'Servers\'][$i][\'hide_connection_errors\'][/doc][/code]' + ), + E_USER_ERROR + ); } + return false; } diff --git a/libraries/classes/Display/Results.php b/libraries/classes/Display/Results.php index 42cdc0f218..39a27b7715 100644 --- a/libraries/classes/Display/Results.php +++ b/libraries/classes/Display/Results.php @@ -1609,10 +1609,9 @@ class Results } $tmp_image = ''
-                     . $tmp_txt . ''; - $tmp_url = 'sql.php' . Url::getCommon($url_params_full_text); + . $tmp_txt . '" title="' . $tmp_txt . '" />'; - return Util::linkOrButton($tmp_url, $tmp_image); + return Util::linkOrButton('sql.php', $url_params_full_text, $tmp_image); } /** @@ -1730,16 +1729,14 @@ class Results 'session_max_rows' => $session_max_rows, 'is_browse_distinct' => $this->__get('is_browse_distinct'), ]; - $single_order_url = 'sql.php' . Url::getCommon($_single_url_params); - $multi_order_url = 'sql.php' . Url::getCommon($_multi_url_params); // Displays the sorting URL // enable sort order swapping for image $order_link = $this->_getSortOrderLink( $order_img, $fields_meta, - $single_order_url, - $multi_order_url + $_single_url_params, + $_multi_url_params ); $sorted_header_html .= $this->_getDraggableClassForSortableColumns( @@ -2041,10 +2038,10 @@ class Results /** * Get sort order link * - * @param string $order_img the sort order image - * @param stdClass $fields_meta set of field properties - * @param string $order_url the url for sort - * @param string $multi_order_url the url for sort + * @param string $order_img the sort order image + * @param array $fields_meta set of field properties + * @param array $order_url_params the url params for sort + * @param array $multi_order_url_params the url params for sort * * @return string the sort order link * @@ -2055,8 +2052,8 @@ class Results private function _getSortOrderLink( $order_img, $fields_meta, - $order_url, - $multi_order_url + $order_url_params, + $multi_order_url_params ) { $order_link_params = [ 'class' => 'sortlink', @@ -2064,10 +2061,13 @@ class Results $order_link_content = htmlspecialchars($fields_meta->name); $inner_link_content = $order_link_content . $order_img - . ''; + . ''; return Util::linkOrButton( - $order_url, + 'sql.php', + $order_url_params, $inner_link_content, $order_link_params ); @@ -2517,7 +2517,7 @@ class Results // 1. Prepares the row // In print view these variable needs to be initialized - $del_url = $del_str = $edit_anchor_class + $del_url = $del_str = $edit_anchor_class = $editCopyUrlParams = $delUrlParams = $edit_str = $js_conf = $copy_url = $copy_str = $edit_url = null; // 1.2 Defines the URLs for the modify/delete link(s) @@ -2550,7 +2550,7 @@ class Results // 1.2.1 Modify link(s) - update row case if ($displayParts['edit_lnk'] == self::UPDATE_ROW) { list($edit_url, $copy_url, $edit_str, $copy_str, - $edit_anchor_class) + $edit_anchor_class, $editCopyUrlParams) = $this->_getModifiedLinks( $where_clause, $clause_is_unique, @@ -2559,7 +2559,7 @@ class Results } // end if (1.2.1) // 1.2.2 Delete/Kill link(s) - list($del_url, $del_str, $js_conf) + list($del_url, $del_str, $js_conf, $delUrlParams) = $this->_getDeleteAndKillLinks( $where_clause, $clause_is_unique, @@ -2586,7 +2586,9 @@ class Results $edit_str, $copy_str, $del_str, - $js_conf + $js_conf, + $editCopyUrlParams, + $delUrlParams ); } elseif ($GLOBALS['cfg']['RowActionLinks'] == self::POSITION_NONE) { $table_body_html .= $this->_getPlacedLinks( @@ -2603,7 +2605,9 @@ class Results $edit_str, $copy_str, $del_str, - $js_conf + $js_conf, + $editCopyUrlParams, + $delUrlParams ); } // end if (1.3) } // end if (1) @@ -2645,7 +2649,9 @@ class Results $edit_str, $copy_str, $del_str, - $js_conf + $js_conf, + $editCopyUrlParams, + $delUrlParams ); } } // end if (3) @@ -3247,15 +3253,9 @@ class Results 'goto' => 'sql.php', ]; - $edit_url = 'tbl_change.php' - . Url::getCommon( - $_url_params + ['default_action' => 'update'] - ); + $edit_url = 'tbl_change.php'; - $copy_url = 'tbl_change.php' - . Url::getCommon( - $_url_params + ['default_action' => 'insert'] - ); + $copy_url = 'tbl_change.php'; $edit_str = $this->_getActionLinkContent( 'b_edit', @@ -3278,6 +3278,7 @@ class Results $edit_str, $copy_str, $edit_anchor_class, + $_url_params ]; } @@ -3330,7 +3331,7 @@ class Results 'message_to_show' => __('The row has been deleted.'), 'goto' => $lnk_goto, ]; - $del_url = 'sql.php' . Url::getCommon($_url_params); + $del_url = 'sql.php'; $js_conf = 'DELETE FROM ' . Sanitize::jsFormat($this->__get('table')) . ' WHERE ' . Sanitize::jsFormat($where_clause, false) @@ -3355,20 +3356,21 @@ class Results 'goto' => $lnk_goto, ]; - $del_url = 'sql.php' . Url::getCommon($_url_params); + $del_url = 'sql.php'; $js_conf = $kill; $del_str = Util::getIcon( 'b_drop', __('Kill') ); } else { - $del_url = $del_str = $js_conf = null; + $del_url = $del_str = $js_conf = $_url_params = null; } return [ $del_url, $del_str, $js_conf, + $_url_params ]; } @@ -3415,20 +3417,22 @@ class Results /** * Prepare placed links * - * @param string $dir the direction of links should place - * @param string $del_url the url for delete row - * @param array $displayParts which elements to display - * @param integer $row_no the index of current row - * @param string $where_clause the where clause of the sql - * @param string $where_clause_html the html encoded where clause - * @param array $condition_array array of keys (primary, unique, condition) - * @param string $edit_url the url for edit row - * @param string $copy_url the url for copy row - * @param string $edit_anchor_class the class for html element for edit - * @param string $edit_str the label for edit row - * @param string $copy_str the label for copy row - * @param string $del_str the label for delete row - * @param string|null $js_conf text for the JS confirmation + * @param string $dir the direction of links should place + * @param string $del_url the url for delete row + * @param array $displayParts which elements to display + * @param integer $row_no the index of current row + * @param string $where_clause the where clause of the sql + * @param string $where_clause_html the html encoded where clause + * @param array $condition_array array of keys (primary, unique, condition) + * @param string $edit_url the url for edit row + * @param string $copy_url the url for copy row + * @param string $edit_anchor_class the class for html element for edit + * @param string $edit_str the label for edit row + * @param string $copy_str the label for copy row + * @param string $del_str the label for delete row + * @param string $js_conf text for the JS confirmation + * @param array $editCopyUrlParams URL parameters + * @param array $delUrlParams URL parameters * * @return string html content * @@ -3450,7 +3454,9 @@ class Results $edit_str, $copy_str, $del_str, - ?string $js_conf + $js_conf, + $editCopyUrlParams, + $delUrlParams ) { if (! isset($js_conf)) { @@ -3471,7 +3477,9 @@ class Results $edit_str, $copy_str, $del_str, - $js_conf + $js_conf, + $editCopyUrlParams, + $delUrlParams ); } @@ -4781,8 +4789,8 @@ class Results /** * Generates HTML to display the Create view in span tag * - * @param array $analyzed_sql_results analyzed sql results - * @param string $url_query String with URL Parameters + * @param array $analyzed_sql_results analyzed sql results + * @param array $urlParams URL Parameters * * @return string * @@ -4790,13 +4798,14 @@ class Results * * @see _getResultsOperations() */ - private function _getLinkForCreateView(array $analyzed_sql_results, $url_query) + private function _getLinkForCreateView(array $analyzed_sql_results, $urlParams) { $results_operations_html = ''; if (empty($analyzed_sql_results['procedure'])) { $results_operations_html .= '' . Util::linkOrButton( - 'view_create.php' . $url_query, + 'view_create.php', + $urlParams, Util::getIcon( 'b_view_add', __('Create view'), @@ -4843,6 +4852,7 @@ class Results { return Util::linkOrButton( '#', + null, Util::getIcon( 'b_insrow', __('Copy to clipboard'), @@ -4866,6 +4876,7 @@ class Results { return Util::linkOrButton( '#', + null, Util::getIcon( 'b_print', __('Print'), @@ -4911,7 +4922,6 @@ class Results 'printview' => '1', 'sql_query' => $this->__get('sql_query'), ]; - $url_query = Url::getCommon($_url_params); if (! $header_shown) { $results_operations_html .= $header; @@ -4921,8 +4931,7 @@ class Results // show only view and not other options if ($only_view) { $results_operations_html .= $this->_getLinkForCreateView( - $analyzed_sql_results, - $url_query + $analyzed_sql_results, $_url_params ); if ($header_shown) { @@ -4976,7 +4985,8 @@ class Results } $results_operations_html .= Util::linkOrButton( - 'tbl_export.php' . Url::getCommon($_url_params), + 'tbl_export.php', + $_url_params, Util::getIcon( 'b_tblexport', __('Export'), @@ -4987,7 +4997,8 @@ class Results // prepare chart $results_operations_html .= Util::linkOrButton( - 'tbl_chart.php' . Url::getCommon($_url_params), + 'tbl_chart.php', + $_url_params, Util::getIcon( 'b_chart', __('Display chart'), @@ -5009,8 +5020,8 @@ class Results if ($geometry_found) { $results_operations_html .= Util::linkOrButton( - 'tbl_gis_visualization.php' - . Url::getCommon($_url_params), + 'tbl_gis_visualization.php', + $_url_params, Util::getIcon( 'b_globe', __('Visualize GIS data'), @@ -5035,8 +5046,7 @@ class Results } $results_operations_html .= $this->_getLinkForCreateView( - $analyzed_sql_results, - $url_query + $analyzed_sql_results, $_url_params ); if ($header_shown) { @@ -5364,7 +5374,8 @@ class Results $tag_params['class'] = 'ajax'; } $result .= Util::linkOrButton( - 'sql.php' . Url::getCommon($_url_params), + 'sql.php', + $_url_params, $displayedData, $tag_params ); @@ -5438,6 +5449,7 @@ class Results * Prepares an Edit link * * @param string $edit_url edit url + * @param array $urlParams URL parameters * @param string $class css classes for td element * @param string $edit_str text for the edit link * @param string $where_clause where clause @@ -5451,6 +5463,7 @@ class Results */ private function _getEditLink( $edit_url, + $urlParams, $class, $edit_str, $where_clause, @@ -5460,7 +5473,7 @@ class Results if (! empty($edit_url)) { $ret .= '' . '' - . Util::linkOrButton($edit_url, $edit_str); + . Util::linkOrButton($edit_url, $urlParams, $edit_str); /* * Where clause for selecting this row uniquely is provided as * a hidden input. Used by jQuery scripts for handling grid editing @@ -5479,6 +5492,7 @@ class Results * Prepares an Copy link * * @param string $copy_url copy url + * @param array $urlParams URL parameters * @param string $copy_str text for the copy link * @param string $where_clause where clause * @param string $where_clause_html url encoded where clause @@ -5492,6 +5506,7 @@ class Results */ private function _getCopyLink( $copy_url, + $urlParams, $copy_str, $where_clause, $where_clause_html, @@ -5504,8 +5519,8 @@ class Results $ret .= $class . ' '; } - $ret .= 'center print_ignore">' - . Util::linkOrButton($copy_url, $copy_str); + $ret .= 'center print_ignore" ' . ' >' + . Util::linkOrButton($copy_url, $urlParams, $copy_str); /* * Where clause for selecting this row uniquely is provided as @@ -5524,10 +5539,11 @@ class Results /** * Prepares a Delete link * - * @param string $del_url delete url - * @param string $del_str text for the delete link - * @param string $js_conf text for the JS confirmation - * @param string $class css classes for the td element + * @param string $del_url delete url + * @param array $delUrlParams URL parameters + * @param string $del_str text for the delete link + * @param string $js_conf text for the JS confirmation + * @param string $class css classes for the td element * * @return string the generated HTML * @@ -5535,7 +5551,7 @@ class Results * * @see _getTableBody(), _getCheckboxAndLinks() */ - private function _getDeleteLink($del_url, $del_str, $js_conf, $class) + private function _getDeleteLink($del_url, $delUrlParams, $del_str, $js_conf, $class) { $ret = ''; @@ -5551,6 +5567,7 @@ class Results $ret .= 'center print_ignore">' . Util::linkOrButton( $del_url, + $delUrlParams, $del_str, ['class' => 'delete_row requireConfirm' . $ajax] ) @@ -5579,6 +5596,8 @@ class Results * @param string $copy_str text for the copy link * @param string $del_str text for the delete link * @param string $js_conf text for the JS confirmation + * @param array $editCopyUrlParams URL parameters + * @param array $delUrlParams URL parameters * * @return string the generated HTML * @@ -5600,13 +5619,17 @@ class Results $edit_str, $copy_str, $del_str, - $js_conf + $js_conf, + $editCopyUrlParams, + $delUrlParams ) { $ret = ''; + $editUrlParams = $editCopyUrlParams + ['default_action' => 'update']; + $copyUrlParams = $editCopyUrlParams + ['default_action' => 'insert']; if ($position == self::POSITION_LEFT) { $ret .= $this->_getCheckboxForMultiRowSubmissions( - $del_url, + $del_url . Url::getCommon($delUrlParams), $displayParts, $row_no, $where_clause_html, @@ -5617,6 +5640,7 @@ class Results $ret .= $this->_getEditLink( $edit_url, + $editUrlParams, $class, $edit_str, $where_clause, @@ -5625,18 +5649,22 @@ class Results $ret .= $this->_getCopyLink( $copy_url, + $copyUrlParams, $copy_str, $where_clause, $where_clause_html, '' ); - $ret .= $this->_getDeleteLink($del_url, $del_str, $js_conf, ''); + $ret .= $this->_getDeleteLink($del_url, $delUrlParams, $del_str, $js_conf, ''); + } elseif ($position == self::POSITION_RIGHT) { - $ret .= $this->_getDeleteLink($del_url, $del_str, $js_conf, ''); + + $ret .= $this->_getDeleteLink($del_url, $delUrlParams, $del_str, $js_conf, ''); $ret .= $this->_getCopyLink( $copy_url, + $copyUrlParams, $copy_str, $where_clause, $where_clause_html, @@ -5645,6 +5673,7 @@ class Results $ret .= $this->_getEditLink( $edit_url, + $editUrlParams, $class, $edit_str, $where_clause, @@ -5652,7 +5681,7 @@ class Results ); $ret .= $this->_getCheckboxForMultiRowSubmissions( - $del_url, + $del_url . Url::getCommon($delUrlParams), $displayParts, $row_no, $where_clause_html, @@ -5662,7 +5691,7 @@ class Results ); } else { // $position == self::POSITION_NONE $ret .= $this->_getCheckboxForMultiRowSubmissions( - $del_url, + $del_url . Url::getCommon($delUrlParams), $displayParts, $row_no, $where_clause_html, diff --git a/libraries/classes/ErrorReport.php b/libraries/classes/ErrorReport.php index b7e60de56b..e86b9ec357 100644 --- a/libraries/classes/ErrorReport.php +++ b/libraries/classes/ErrorReport.php @@ -218,6 +218,7 @@ class ErrorReport unset($queryArray["table"]); unset($queryArray["token"]); unset($queryArray["server"]); + unset($queryArray["eq"]); $query = http_build_query($queryArray); } else { $query = ''; diff --git a/libraries/classes/Export.php b/libraries/classes/Export.php index 67a64186c1..8ef1b85cf1 100644 --- a/libraries/classes/Export.php +++ b/libraries/classes/Export.php @@ -499,16 +499,17 @@ class Export */ $back_button = '

[ ' . ' ' . Util::getIcon('b_edit', __('Edit')) . '' . '' . "\n"; $this_params = $GLOBALS['url_params']; @@ -764,7 +764,8 @@ class Index $r .= ''; $r .= Util::linkOrButton( - 'sql.php' . Url::getCommon($this_params), + 'sql.php', + $this_params, Util::getIcon('b_drop', __('Drop')), ['class' => 'drop_primary_key_index_anchor ajax'] ); diff --git a/libraries/classes/InsertEdit.php b/libraries/classes/InsertEdit.php index 2fcf941835..5ca66410aa 100644 --- a/libraries/classes/InsertEdit.php +++ b/libraries/classes/InsertEdit.php @@ -295,12 +295,12 @@ class InsertEdit if (! $is_show) { return ' : ' + . Url::getCommon($this_url_params, '', false) . '">' . $this->showTypeOrFunctionLabel($which) . ''; } return '' . $this->showTypeOrFunctionLabel($which) . ''; @@ -888,7 +888,7 @@ class InsertEdit 'rownumber' => $rownumber, 'data' => $data, ], - '' + '', false ) . '">' . str_replace("'", "\'", $titles['Browse']) . ''; return $html_output; @@ -1740,6 +1740,7 @@ class InsertEdit return '' . Util::linkOrButton( '#', + null, $edit_str, [], '_blank' diff --git a/libraries/classes/Navigation/Navigation.php b/libraries/classes/Navigation/Navigation.php index 6505619252..5223367ebf 100644 --- a/libraries/classes/Navigation/Navigation.php +++ b/libraries/classes/Navigation/Navigation.php @@ -92,7 +92,8 @@ class Navigation $hasStartChar = strpos($logo['link'], '?'); $logo['link'] .= Url::getCommon( [], - is_bool($hasStartChar) ? '?' : Url::getArgSeparator() + is_bool($hasStartChar) ? '?' : Url::getArgSeparator(), + false ); } $logo['attributes'] = ''; diff --git a/libraries/classes/Navigation/NavigationTree.php b/libraries/classes/Navigation/NavigationTree.php index a90d750518..d4f97b6f11 100644 --- a/libraries/classes/Navigation/NavigationTree.php +++ b/libraries/classes/Navigation/NavigationTree.php @@ -123,13 +123,13 @@ class NavigationTree $this->pos = $this->getNavigationDbPos(); } // Get the active node - if (isset($_REQUEST['aPath'])) { - $this->aPath[0] = $this->parsePath($_REQUEST['aPath']); - $this->pos2Name[0] = $_REQUEST['pos2_name']; - $this->pos2Value[0] = (int) $_REQUEST['pos2_value']; - if (isset($_REQUEST['pos3_name'])) { - $this->pos3Name[0] = $_REQUEST['pos3_name']; - $this->pos3Value[0] = (int) $_REQUEST['pos3_value']; + if (isset($_POST['aPath'])) { + $this->aPath[0] = $this->parsePath($_POST['aPath']); + $this->pos2Name[0] = $_POST['pos2_name']; + $this->pos2Value[0] = (int) $_POST['pos2_value']; + if (isset($_POST['pos3_name'])) { + $this->pos3Name[0] = $_POST['pos3_name']; + $this->pos3Value[0] = (int) $_POST['pos3_value']; } } else { if (isset($_POST['n0_aPath'])) { @@ -150,8 +150,8 @@ class NavigationTree } } } - if (isset($_REQUEST['vPath'])) { - $this->vPath[0] = $this->parsePath($_REQUEST['vPath']); + if (isset($_POST['vPath'])) { + $this->vPath[0] = $this->parsePath($_POST['vPath']); } else { if (isset($_POST['n0_vPath'])) { $count = 0; @@ -163,11 +163,11 @@ class NavigationTree } } } - if (isset($_REQUEST['searchClause'])) { - $this->searchClause = $_REQUEST['searchClause']; + if (isset($_POST['searchClause'])) { + $this->searchClause = $_POST['searchClause']; } - if (isset($_REQUEST['searchClause2'])) { - $this->searchClause2 = $_REQUEST['searchClause2']; + if (isset($_POST['searchClause2'])) { + $this->searchClause2 = $_POST['searchClause2']; } // Initialise the tree by creating a root node $node = NodeFactory::getInstance('NodeDatabaseContainer', 'root'); @@ -1168,7 +1168,7 @@ class NavigationTree } foreach ($icons as $key => $icon) { - $link = vsprintf($iconLinks[$key], $args); + $link = $this->encryptQueryParams(vsprintf($iconLinks[$key], $args)); if ($linkClass != '') { $retval .= ""; $retval .= "{$icon}"; @@ -1186,7 +1186,7 @@ class NavigationTree foreach ($node->parents(true) as $parent) { $args[] = urlencode($parent->realName); } - $link = vsprintf($node->links['text'], $args); + $link = $this->encryptQueryParams(vsprintf($node->links['text'], $args)); $title = isset($node->links['title']) ? $node->links['title'] : $node->title ?? ''; if ($node->type == Node::CONTAINER) { $retval .= " "; @@ -1572,4 +1572,23 @@ class NavigationTree return $retval; } + + /** + * @param string $link + * + * @return string + */ + private function encryptQueryParams($link) + { + global $PMA_Config; + + if (! $PMA_Config->get('URLQueryEncryption')) { + return $link; + } + + $url = parse_url($link); + parse_str(htmlspecialchars_decode($url['query']), $query); + + return $url['path'] . '?' . htmlspecialchars(Url::buildHttpQuery($query)); + } } diff --git a/libraries/classes/Navigation/Nodes/NodeDatabase.php b/libraries/classes/Navigation/Nodes/NodeDatabase.php index 862dce7c11..18a1de0ad9 100644 --- a/libraries/classes/Navigation/Nodes/NodeDatabase.php +++ b/libraries/classes/Navigation/Nodes/NodeDatabase.php @@ -681,7 +681,7 @@ class NodeDatabase extends Node ]; $ret = '' . '' . Util::getImage( 'show', diff --git a/libraries/classes/Navigation/Nodes/NodeDatabaseChild.php b/libraries/classes/Navigation/Nodes/NodeDatabaseChild.php index 0005915ade..b065a9d331 100644 --- a/libraries/classes/Navigation/Nodes/NodeDatabaseChild.php +++ b/libraries/classes/Navigation/Nodes/NodeDatabaseChild.php @@ -51,7 +51,7 @@ abstract class NodeDatabaseChild extends Node $ret = '' . '' . Util::getImage('hide', __('Hide')) . ''; diff --git a/libraries/classes/Operations.php b/libraries/classes/Operations.php index 54650237eb..c9987d8afc 100644 --- a/libraries/classes/Operations.php +++ b/libraries/classes/Operations.php @@ -1589,7 +1589,8 @@ class Operations { return '

  • ' . Util::linkOrButton( - 'sql.php' . Url::getCommon(array_merge($url_params, $params)), + 'sql.php', + array_merge($url_params, $params), $action_message, ['class' => 'maintain_action ajax'] ) @@ -1649,7 +1650,8 @@ class Operations public function getDeleteDataOrTablelink(array $url_params, $syntax, $link, $htmlId) { return '
  • ' . Util::linkOrButton( - 'sql.php' . Url::getCommon($url_params), + 'sql.php', + $url_params, $link, [ 'id' => $htmlId, diff --git a/libraries/classes/Plugins/Auth/AuthenticationSignon.php b/libraries/classes/Plugins/Auth/AuthenticationSignon.php index 95508a5e91..2753807d2a 100644 --- a/libraries/classes/Plugins/Auth/AuthenticationSignon.php +++ b/libraries/classes/Plugins/Auth/AuthenticationSignon.php @@ -12,6 +12,7 @@ namespace PhpMyAdmin\Plugins\Auth; use PhpMyAdmin\Core; use PhpMyAdmin\Plugins\AuthenticationPlugin; +use PhpMyAdmin\Response; use PhpMyAdmin\Util; /** @@ -28,6 +29,7 @@ class AuthenticationSignon extends AuthenticationPlugin */ public function showLoginForm() { + Response::getInstance()->disable(); unset($_SESSION['LAST_SIGNON_URL']); if (empty($GLOBALS['cfg']['Server']['SignonURL'])) { Core::fatalError('You must set SignonURL!'); diff --git a/libraries/classes/ReplicationGui.php b/libraries/classes/ReplicationGui.php index fc853abfc9..87c8951d40 100644 --- a/libraries/classes/ReplicationGui.php +++ b/libraries/classes/ReplicationGui.php @@ -133,7 +133,7 @@ class ReplicationGui } $urlParams['sr_slave_control_parm'] = 'IO_THREAD'; - $slaveControlIoLink = Url::getCommon($urlParams, ''); + $slaveControlIoLink = Url::getCommon($urlParams, '', false); if ($serverSlaveReplication[0]['Slave_SQL_Running'] == 'No') { $urlParams['sr_slave_action'] = 'start'; @@ -142,7 +142,7 @@ class ReplicationGui } $urlParams['sr_slave_control_parm'] = 'SQL_THREAD'; - $slaveControlSqlLink = Url::getCommon($urlParams, ''); + $slaveControlSqlLink = Url::getCommon($urlParams, '', false); if ($serverSlaveReplication[0]['Slave_IO_Running'] == 'No' || $serverSlaveReplication[0]['Slave_SQL_Running'] == 'No' @@ -153,21 +153,21 @@ class ReplicationGui } $urlParams['sr_slave_control_parm'] = null; - $slaveControlFullLink = Url::getCommon($urlParams, ''); + $slaveControlFullLink = Url::getCommon($urlParams, '', false); $urlParams['sr_slave_action'] = 'reset'; - $slaveControlResetLink = Url::getCommon($urlParams, ''); + $slaveControlResetLink = Url::getCommon($urlParams, '', false); $urlParams = $GLOBALS['url_params']; $urlParams['sr_take_action'] = true; $urlParams['sr_slave_skip_error'] = true; - $slaveSkipErrorLink = Url::getCommon($urlParams, ''); + $slaveSkipErrorLink = Url::getCommon($urlParams, '', false); $urlParams = $GLOBALS['url_params']; $urlParams['sl_configure'] = true; $urlParams['repl_clear_scr'] = true; - $reconfigureMasterLink = Url::getCommon($urlParams, ''); + $reconfigureMasterLink = Url::getCommon($urlParams, '', false); $slaveStatusTable = $this->getHtmlForReplicationStatusTable('slave', true, false); diff --git a/libraries/classes/Rte/RteList.php b/libraries/classes/Rte/RteList.php index 0e87c6c4e6..ad39db3fbb 100644 --- a/libraries/classes/Rte/RteList.php +++ b/libraries/classes/Rte/RteList.php @@ -221,7 +221,7 @@ class RteList */ public function getRoutineRow(array $routine, $rowclass = '') { - global $url_query, $db, $titles; + global $url_query, $url_params, $db, $titles; $sql_drop = sprintf( 'DROP %s IF EXISTS %s', @@ -348,7 +348,11 @@ class RteList $retval .= " \n"; $retval .= " \n"; $retval .= Util::linkOrButton( - 'sql.php' . $url_query . '&sql_query=' . urlencode($sql_drop) . '&goto=db_routines.php' . urlencode("?db={$db}"), + 'sql.php', + array_merge( + $url_params, + ['sql_query' => $sql_drop, 'goto' => 'db_routines.php' . Url::getCommon(['db' => $db])] + ), $titles['Drop'], ['class' => 'ajax drop_anchor'] ); @@ -375,7 +379,7 @@ class RteList */ public function getTriggerRow(array $trigger, $rowclass = '') { - global $url_query, $db, $table, $titles; + global $url_query, $url_params, $db, $table, $titles; $retval = " \n"; $retval .= " \n"; @@ -392,8 +396,8 @@ class RteList $retval .= " \n"; if (empty($table)) { $retval .= " \n"; - $retval .= "" + $retval .= " $trigger['table']])) . "'>" . htmlspecialchars($trigger['table']) . ""; $retval .= " \n"; } @@ -422,7 +426,11 @@ class RteList $retval .= " \n"; if (Util::currentUserHasPrivilege('TRIGGER', $db)) { $retval .= Util::linkOrButton( - 'sql.php' . $url_query . '&sql_query=' . urlencode($trigger['drop']) . '&goto=db_triggers.php' . urlencode("?db={$db}"), + 'sql.php', + array_merge( + $url_params, + ['sql_query' => $trigger['drop'], 'goto' => 'db_triggers.php' . Url::getCommon(['db' => $db])] + ), $titles['Drop'], ['class' => 'ajax drop_anchor'] ); @@ -451,7 +459,7 @@ class RteList */ public function getEventRow(array $event, $rowclass = '') { - global $url_query, $db, $titles; + global $url_query, $url_params, $db, $titles; $sql_drop = sprintf( 'DROP EVENT IF EXISTS %s', @@ -500,7 +508,11 @@ class RteList $retval .= " \n"; if (Util::currentUserHasPrivilege('EVENT', $db)) { $retval .= Util::linkOrButton( - 'sql.php' . $url_query . '&sql_query=' . urlencode($sql_drop) . '&goto=db_events.php' . urlencode("?db={$db}"), + 'sql.php', + array_merge( + $url_params, + ['sql_query' => $sql_drop, 'goto' => 'db_events.php' . Url::getCommon(['db' => $db])] + ), $titles['Drop'], ['class' => 'ajax drop_anchor'] ); diff --git a/libraries/classes/Server/Privileges.php b/libraries/classes/Server/Privileges.php index 1f24748d89..79b319fc77 100644 --- a/libraries/classes/Server/Privileges.php +++ b/libraries/classes/Server/Privileges.php @@ -2985,7 +2985,7 @@ class Privileges $html .= ' href="server_privileges.php'; if ($linktype == 'revoke') { - $html .= '" data-post="' . Url::getCommon($params, ''); + $html .= '" data-post="' . Url::getCommon($params, '', false); } else { $html .= Url::getCommon($params); } diff --git a/libraries/classes/Server/UserGroups.php b/libraries/classes/Server/UserGroups.php index 89bc1a32eb..6bbc6f31c0 100644 --- a/libraries/classes/Server/UserGroups.php +++ b/libraries/classes/Server/UserGroups.php @@ -118,7 +118,7 @@ class UserGroups 'viewUsers' => 1, 'userGroup' => $groupName, ], - '' + '', false ) . '">' . Util::getIcon('b_usrlist', __('View users')) @@ -130,7 +130,7 @@ class UserGroups 'editUserGroup' => 1, 'userGroup' => $groupName, ], - '' + '', false ) . '">' . Util::getIcon('b_edit', __('Edit')) . ''; @@ -142,7 +142,7 @@ class UserGroups 'deleteUserGroup' => 1, 'userGroup' => $groupName, ], - '' + '', false ) . '">' . Util::getIcon('b_drop', __('Delete')) . ''; diff --git a/libraries/classes/Tracking.php b/libraries/classes/Tracking.php index a0a7c1d7e0..6c4361df83 100644 --- a/libraries/classes/Tracking.php +++ b/libraries/classes/Tracking.php @@ -649,7 +649,7 @@ class Tracking 'report' => 'true', 'version' => $_POST['version'], $deleteParam => $lineNumber - $offset, - ], ''); + ], '', false); $entry['line_number'] = $lineNumber; $entries[] = $entry; } diff --git a/libraries/classes/Url.php b/libraries/classes/Url.php index b9bd013425..de31cc8681 100644 --- a/libraries/classes/Url.php +++ b/libraries/classes/Url.php @@ -9,6 +9,8 @@ declare(strict_types=1); namespace PhpMyAdmin; +use PhpMyAdmin\Crypto\Crypto; + /** * Static methods for URL/hidden inputs generating * @@ -161,14 +163,15 @@ class Url * * @param mixed $params optional, Contains an associative array with url params * @param string $divider optional character to use instead of '?' + * @param bool $encrypt whether to encrypt URL params * * @return string string with URL parameters * @access public */ - public static function getCommon($params = [], $divider = '?') + public static function getCommon($params = [], $divider = '?', $encrypt = true) { return htmlspecialchars( - Url::getCommonRaw($params, $divider) + Url::getCommonRaw($params, $divider, $encrypt) ); } @@ -197,15 +200,15 @@ class Url * * @param mixed $params optional, Contains an associative array with url params * @param string $divider optional character to use instead of '?' + * @param bool $encrypt whether to encrypt URL params * * @return string string with URL parameters * @access public */ - public static function getCommonRaw($params = [], $divider = '?') + public static function getCommonRaw($params = [], $divider = '?', $encrypt = true) { /** @var Config $PMA_Config */ global $PMA_Config; - $separator = Url::getArgSeparator(); // avoid overwriting when creating navi panel links to servers if (isset($GLOBALS['server']) @@ -222,7 +225,7 @@ class Url $params['lang'] = $GLOBALS['lang']; } - $query = http_build_query($params, '', $separator); + $query = self::buildHttpQuery($params, $encrypt); if ($divider != '?' || strlen($query) > 0) { return $divider . $query; @@ -231,6 +234,81 @@ class Url return ''; } + /** + * @param array $params + * @param bool $encrypt whether to encrypt URL params + * + * @return string + */ + public static function buildHttpQuery($params, $encrypt = true) + { + global $PMA_Config; + + $separator = self::getArgSeparator(); + + if (! $encrypt || ! $PMA_Config->get('URLQueryEncryption')) { + return http_build_query($params, '', $separator); + } + + $data = $params; + $keys = [ + 'db', + 'table', + 'field', + 'sql_query', + 'sql_signature', + 'where_clause', + 'goto', + 'back', + 'message_to_show', + 'username', + 'hostname', + 'dbname', + 'tablename', + 'checkprivsdb', + 'checkprivstable', + ]; + $paramsToEncrypt = []; + foreach ($params as $paramKey => $paramValue) { + if (! in_array($paramKey, $keys)) { + continue; + } + + $paramsToEncrypt[$paramKey] = $paramValue; + unset($data[$paramKey]); + } + + if ($paramsToEncrypt !== []) { + $data['eq'] = self::encryptQuery(json_encode($paramsToEncrypt)); + } + + return http_build_query($data, '', $separator); + } + + /** + * @param string $query + * + * @return string + */ + public static function encryptQuery($query) + { + $crypto = new Crypto(); + + return strtr(base64_encode($crypto->encrypt($query)), '+/', '-_'); + } + + /** + * @param string $query + * + * @return string|null + */ + public static function decryptQuery($query) + { + $crypto = new Crypto(); + + return $crypto->decrypt(base64_decode(strtr($query, '-_', '+/'))); + } + /** * Returns url separator * diff --git a/libraries/classes/Util.php b/libraries/classes/Util.php index d08ad565bd..1abf8b44db 100644 --- a/libraries/classes/Util.php +++ b/libraries/classes/Util.php @@ -1096,7 +1096,8 @@ class Util $explain_params['sql_query'] = 'EXPLAIN ' . $sql_query; $explain_link = ' [ ' . self::linkOrButton( - 'import.php' . Url::getCommon($explain_params), + 'import.php', + $explain_params, __('Explain SQL') ) . ' ]'; } elseif (preg_match( @@ -1107,7 +1108,8 @@ class Util = mb_substr($sql_query, 8); $explain_link = ' [ ' . self::linkOrButton( - 'import.php' . Url::getCommon($explain_params), + 'import.php', + $explain_params, __('Skip Explain SQL') ) . ']'; $url = 'https://mariadb.org/explain_analyzer/analyze/' @@ -1116,6 +1118,7 @@ class Util $explain_link .= ' [' . self::linkOrButton( htmlspecialchars('url.php?url=' . urlencode($url)), + null, sprintf(__('Analyze Explain at %s'), 'mariadb.org'), [], '_blank' @@ -1131,9 +1134,8 @@ class Util if (! empty($cfg['SQLQuery']['Edit']) && empty($GLOBALS['show_as_php']) ) { - $edit_link .= Url::getCommon($url_params); $edit_link = ' [ ' - . self::linkOrButton($edit_link, __('Edit')) + . self::linkOrButton($edit_link, $url_params, __('Edit')) . ' ]'; } else { $edit_link = ''; @@ -1145,14 +1147,16 @@ class Util if (! empty($GLOBALS['show_as_php'])) { $php_link = ' [ ' . self::linkOrButton( - 'import.php' . Url::getCommon($url_params), + 'import.php', + $url_params, __('Without PHP code') ) . ' ]'; $php_link .= ' [ ' . self::linkOrButton( - 'import.php' . Url::getCommon($url_params), + 'import.php', + $url_params, __('Submit query') ) . ' ]'; @@ -1161,7 +1165,8 @@ class Util $php_params['show_as_php'] = 1; $php_link = ' [ ' . self::linkOrButton( - 'import.php' . Url::getCommon($php_params), + 'import.php', + $php_params, __('Create PHP code') ) . ' ]'; @@ -1177,7 +1182,7 @@ class Util ) { $refresh_link = 'sql.php' . Url::getCommon($url_params); $refresh_link = ' [ ' - . self::linkOrButton($refresh_link, __('Refresh')) . ']'; + . self::linkOrButton('import.php', $url_params, __('Refresh')) . ']'; } else { $refresh_link = ''; } //refresh @@ -1216,6 +1221,7 @@ class Util $inline_edit_link = ' [' . self::linkOrButton( '#', + null, _pgettext('Inline edit query', 'Edit inline'), ['class' => 'inline_edit_sql'] ) @@ -1785,20 +1791,26 @@ class Util * - URL components are over Suhosin limits * - There is SQL query in the parameters * - * @param string $url the URL - * @param string $message the link message - * @param mixed $tag_params string: js confirmation; array: additional tag - * params (f.e. style="") - * @param string $target target + * @param string $urlPath the URL + * @param array|null $urlParams URL parameters + * @param string $message the link message + * @param mixed $tag_params string: js confirmation; array: additional tag params (f.e. style="") + * @param string $target target * * @return string the results to be echoed or saved in an array */ public static function linkOrButton( - $url, + $urlPath, + $urlParams, $message, $tag_params = [], $target = '' ) { + $url = $urlPath; + if (is_array($urlParams)) { + $url = $urlPath . Url::getCommon($urlParams, '?', false); + } + $url_length = strlen($url); if (! is_array($tag_params)) { @@ -1857,6 +1869,11 @@ class Util ) { $url .= '?' . explode('&', $parts[1], 2)[0]; } + } else { + $url = $urlPath; + if (is_array($urlParams)) { + $url = $urlPath . Url::getCommon($urlParams); + } } foreach ($tag_params as $par_name => $par_value) { @@ -2427,13 +2444,15 @@ class Util $_url_params[$name] = 0; $list_navigator_html .= '' . $caption1 - . ''; + . '" data-post="' + . Url::getCommon($_url_params, '', false) + . '">' . $caption1 . ''; $_url_params[$name] = $pos - $max_count; - $list_navigator_html .= ' ' - . $caption2 . ''; + $list_navigator_html .= ' ' . $caption2 . ''; } $list_navigator_html .= '
    ' . $caption3 - . ''; + . '" data-post="' + . Url::getCommon($_url_params, '', false) + . '" >' . $caption3 . ''; $_url_params[$name] = floor($count / $max_count) * $max_count; if ($_url_params[$name] == $count) { $_url_params[$name] = $count - $max_count; } - $list_navigator_html .= ' ' - . $caption4 . ''; + $list_navigator_html .= ' ' . $caption4 . ''; } $list_navigator_html .= '' . "\n"; } @@ -4969,9 +4990,7 @@ class Util $urlParams['tbl_group'] = $_REQUEST['tbl_group']; } - $url = 'db_structure.php' . Url::getCommon($urlParams); - - return self::linkOrButton($url, $title . $orderImg, $orderLinkParams); + return self::linkOrButton('db_structure.php', $urlParams, $title . $orderImg, $orderLinkParams); } /** diff --git a/libraries/common.inc.php b/libraries/common.inc.php index 2c4c10fbf4..ae34c9feff 100644 --- a/libraries/common.inc.php +++ b/libraries/common.inc.php @@ -137,6 +137,8 @@ if (! defined('PMA_NO_SESSION')) { Session::setUp($GLOBALS['PMA_Config'], $GLOBALS['error_handler']); } +Core::populateRequestWithEncryptedQueryParams(); + /** * init some variables LABEL_variables_init */ diff --git a/libraries/config.default.php b/libraries/config.default.php index 7eef0e0711..9d9fec5c2c 100644 --- a/libraries/config.default.php +++ b/libraries/config.default.php @@ -606,6 +606,13 @@ $cfg['Servers'][$i]['tracking_add_drop_table'] = true; */ $cfg['Servers'][$i]['tracking_add_drop_database'] = true; +/** + * Whether to show or hide detailed MySQL/MariaDB connection errors on the login page. + * + * @global bool $cfg['Servers'][$i]['hide_connection_errors'] + */ +$cfg['Servers'][$i]['hide_connection_errors'] = false; + /** * Default server (0 = no default server) * @@ -820,6 +827,20 @@ $cfg['UseDbSearch'] = true; */ $cfg['IgnoreMultiSubmitErrors'] = false; +/** + * Define whether phpMyAdmin will encrypt sensitive data from the URL query string. + * + * @global bool $cfg['URLQueryEncryption'] + */ +$cfg['URLQueryEncryption'] = false; + +/** + * A secret key used to encrypt/decrypt the URL query string. Should be 32 bytes long. + * + * @global string $cfg['URLQueryEncryptionSecretKey'] + */ +$cfg['URLQueryEncryptionSecretKey'] = ''; + /** * allow login to any user entered server in cookie based authentication * diff --git a/scripts/create-release.sh b/scripts/create-release.sh index 0e357ec063..ccb9117caf 100755 --- a/scripts/create-release.sh +++ b/scripts/create-release.sh @@ -247,20 +247,7 @@ if [ ! -d libraries/tcpdf ] ; then echo "* Running composer" composer config platform.php "$PHP_REQ" composer update --no-dev - - # Parse the required versions from composer.json - PACKAGES_VERSIONS='' - case "$branch" in - QA_4*) PACKAGE_LIST="tecnickcom/tcpdf pragmarx/google2fa bacon/bacon-qr-code samyoul/u2f-php-server" ;; - *) PACKAGE_LIST="tecnickcom/tcpdf pragmarx/google2fa-qrcode samyoul/u2f-php-server" ;; - esac - - for PACKAGES in $PACKAGE_LIST - do - PACKAGES_VERSIONS="$PACKAGES_VERSIONS $PACKAGES:`awk "/require-dev/ {printline = 1; print; next } printline" composer.json | grep "$PACKAGES" | awk -F [\\"] '{print $4}'`" - done - composer require --update-no-dev $PACKAGES_VERSIONS - + composer require --update-no-dev tecnickcom/tcpdf:\^6.3 pragmarx/google2fa:\^3.0.0 bacon/bacon-qr-code:\^1.0.0 samyoul/u2f-php-server:\^1.1 mv composer.json.backup composer.json echo "* Cleanup of composer packages" rm -rf \ @@ -290,8 +277,8 @@ if [ ! -d libraries/tcpdf ] ; then vendor/twig/twig/test \ vendor/google/recaptcha/examples/ \ vendor/google/recaptcha/tests/ - find vendor/phpseclib/phpseclib/phpseclib/Crypt/ -maxdepth 1 -type f -not -name AES.php -not -name Base.php -not -name Random.php -not -name Rijndael.php -print0 | xargs -0 rm - find vendor/tecnickcom/tcpdf/fonts/ -maxdepth 1 -type f -not -name 'dejavusans.*' -not -name 'dejavusansb.*' -not -name 'helvetica.php' -print0 | xargs -0 rm + find vendor/phpseclib/phpseclib/phpseclib/Crypt/ -maxdepth 1 -type f -not -name AES.php -not -name Base.php -not -name Random.php -not -name Rijndael.php -print0 | xargs -0 rm -f + find vendor/tecnickcom/tcpdf/fonts/ -maxdepth 1 -type f -not -name 'dejavusans.*' -not -name 'dejavusansb.*' -not -name 'helvetica.php' -print0 | xargs -0 rm -f if [ $do_tag -eq 1 ] ; then echo "* Commiting composer.lock" git add --force composer.lock diff --git a/templates/database/tracking/tables.twig b/templates/database/tracking/tables.twig index 7f959470d8..bcdd75afaf 100644 --- a/templates/database/tracking/tables.twig +++ b/templates/database/tracking/tables.twig @@ -52,7 +52,7 @@ 'back': 'db_tracking.php', 'table': version.table_name, 'delete_tracking': true - }, '') }}"> + }, '', false) }}"> {{ get_icon('b_drop', 'Delete tracking'|trans) }} @@ -63,7 +63,7 @@ 'goto': 'tbl_tracking.php', 'back': 'db_tracking.php', 'table': version.table_name - }, '') }}"> + }, '', false) }}"> {{ get_icon('b_versions', 'Versions'|trans) }} + }, '', false) }}"> {{ get_icon('b_report', 'Tracking report'|trans) }} + }, '', false) }}"> {{ get_icon('b_props', 'Structure snapshot'|trans) }} diff --git a/templates/server/binlog/index.twig b/templates/server/binlog/index.twig index cdc85dc753..c602ec12c6 100644 --- a/templates/server/binlog/index.twig +++ b/templates/server/binlog/index.twig @@ -43,12 +43,12 @@ {% if has_previous %} {% if has_icons %} - « {% else %} - + {% trans %}Previous{% context %}Previous page{% endtrans %} « {% endif %} @@ -56,11 +56,11 @@ {% endif %} {% if is_full_query %} - + {% trans 'Truncate shown queries' %} {% else %} - + {% trans 'Show full queries' %} {% endif %} @@ -68,12 +68,12 @@ {% if has_next %} - {% if has_icons %} - » {% else %} - + {% trans %}Next{% context %}Next page{% endtrans %} » {% endif %} diff --git a/templates/server/databases/index.twig b/templates/server/databases/index.twig index ebff55fb1b..bbf8410a02 100644 --- a/templates/server/databases/index.twig +++ b/templates/server/databases/index.twig @@ -310,7 +310,7 @@ {{ 'Note: Enabling the database statistics here might cause heavy traffic between the web server and the MySQL server.'|trans|notice }}
    • - + {% trans 'Enable statistics' %}
    • diff --git a/templates/server/replication/master_replication.twig b/templates/server/replication/master_replication.twig index 8ba5d1b535..5a81dce912 100644 --- a/templates/server/replication/master_replication.twig +++ b/templates/server/replication/master_replication.twig @@ -39,7 +39,7 @@
    • - + {% trans 'Add slave replication user' %}
    • diff --git a/templates/server/replication/slave_configuration.twig b/templates/server/replication/slave_configuration.twig index d3b634e325..526ec5edc2 100644 --- a/templates/server/replication/slave_configuration.twig +++ b/templates/server/replication/slave_configuration.twig @@ -102,7 +102,7 @@ {% apply format('', '')|raw %} + }, '', false)) ~ '">', '')|raw %} {% trans 'This server is not configured as slave in a replication process. Would you like to %sconfigure%s it?' %} {% endapply %} {% endif %} diff --git a/templates/server/status/processes/list.twig b/templates/server/status/processes/list.twig index 83561f9040..fe40b224ad 100644 --- a/templates/server/status/processes/list.twig +++ b/templates/server/status/processes/list.twig @@ -5,7 +5,7 @@ {% trans 'Processes' %} {% for column in columns %} - + {{ column.name }} {% if column.is_sorted %} 
@@ -15,7 +15,7 @@
               {% endif %}
             </a>
             {% if column.has_full_query %}
-              <a href= + {% if column.is_full %} {{ get_image( 's_partialtext', @@ -40,7 +40,7 @@ {% for row in rows %} - + {% trans 'Kill' %} diff --git a/templates/setup/home/index.twig b/templates/setup/home/index.twig index a289af7c8c..1642d60f1e 100644 --- a/templates/setup/home/index.twig +++ b/templates/setup/home/index.twig @@ -58,7 +58,7 @@ | + {{- get_common({ token: server.params.token }, '', false) }}"> {% trans 'Delete' %} diff --git a/templates/sql/relational_column_dropdown.twig b/templates/sql/relational_column_dropdown.twig index b77beaae34..b8498ddefd 100644 --- a/templates/sql/relational_column_dropdown.twig +++ b/templates/sql/relational_column_dropdown.twig @@ -1,4 +1,4 @@ {{ current_value }} - + {% trans 'Browse foreign values' %} diff --git a/templates/table/relation/foreign_key_row.twig b/templates/table/relation/foreign_key_row.twig index 3df6c34550..cb1e996226 100644 --- a/templates/table/relation/foreign_key_row.twig +++ b/templates/table/relation/foreign_key_row.twig @@ -22,9 +22,8 @@ {% if one_key['constraint'] is defined %} - {% set drop_url = 'sql.php' ~ get_common(this_params) %} {% set drop_str = get_icon('b_drop', 'Drop'|trans) %} - {{ link_or_button(drop_url, drop_str, {'class': 'drop_foreign_key_anchor ajax'}) }} + {{ link_or_button('sql.php', this_params, drop_str, {'class': 'drop_foreign_key_anchor ajax'}) }} {% endif %} diff --git a/templates/table/search/input_box.twig b/templates/table/search/input_box.twig index a20fca3a9f..e711570005 100644 --- a/templates/table/search/input_box.twig +++ b/templates/table/search/input_box.twig @@ -21,7 +21,7 @@ value="{{ criteria_values[column_index] }}" {% endif %}> {{ titles['Browse']|replace({"'": "\\'"})|raw }} @@ -34,10 +34,9 @@ class="textfield" id="field_{{ column_index }}"> {% if in_fbs %} - {% set edit_url = 'gis_data_editor.php' ~ get_common() %} {% set edit_str = get_icon('b_edit', 'Edit/Insert'|trans) %} - {{ link_or_button(edit_url, edit_str, [], '_blank') }} + {{ link_or_button('gis_data_editor.php', [], edit_str, [], '_blank') }} {% endif %} {% elseif column_type starts with 'enum' diff --git a/templates/table/structure/display_partitions.twig b/templates/table/structure/display_partitions.twig index b10a0bc929..5b3923b5ed 100644 --- a/templates/table/structure/display_partitions.twig +++ b/templates/table/structure/display_partitions.twig @@ -134,7 +134,7 @@ {% if partitions is empty %} {% else %} - {{ link_or_button(remove_url, 'Remove partitioning'|trans, { + {{ link_or_button('sql.php', remove_url_params, 'Remove partitioning'|trans, { 'class': 'button ajax', 'id': 'remove_partitioning' }) }} diff --git a/templates/table/structure/display_structure.twig b/templates/table/structure/display_structure.twig index 16e11dbac2..19a416be44 100644 --- a/templates/table/structure/display_structure.twig +++ b/templates/table/structure/display_structure.twig @@ -372,9 +372,9 @@ {# Work on the table #} ' . 'Copy Copy' . 'Edit Edit' . ' Edit', @@ -821,6 +839,21 @@ class ResultsTest extends PmaTestCase ' Delete', 'DELETE FROM `data`.`new` WHERE `new`.`id` = 1', + [ + 'db' => 'data', + 'table' => 'new', + 'where_clause' => '`new`.`id` = 1', + 'clause_is_unique' => true, + 'sql_query' => 'SELECT * FROM `new`', + 'goto' => 'sql.php', + ], + [ + 'db' => 'data', + 'table' => 'new', + 'sql_query' => 'DELETE FROM `data`.`new` WHERE `new`.`id` = 1', + 'message_to_show' => 'The row has been deleted.', + 'goto' => 'tbl_sql.php', + ], ' Delete', null, + [], + [], 'markTestSkipped('native setlocale failed'); + } + + _setlocale(LC_ALL, 'POSIX'); + if (PHP_INT_SIZE === 8) { $GLOBALS['cfg']['IconvExtraParams'] = '//TRANSLIT'; Encoding::setEngine(Encoding::ENGINE_ICONV); diff --git a/test/classes/FooterTest.php b/test/classes/FooterTest.php index 13c7018b04..fd505c55b8 100644 --- a/test/classes/FooterTest.php +++ b/test/classes/FooterTest.php @@ -48,6 +48,7 @@ class FooterTest extends PmaTestCase $GLOBALS['table'] = ''; $GLOBALS['text_dir'] = 'ltr'; $GLOBALS['PMA_Config'] = new Config(); + $GLOBALS['PMA_Config']->set('URLQueryEncryption', false); $GLOBALS['PMA_Config']->enableBc(); $GLOBALS['cfg']['Server']['DisableIS'] = false; $GLOBALS['cfg']['Server']['verbose'] = 'verbose host'; diff --git a/test/classes/InsertEditTest.php b/test/classes/InsertEditTest.php index c0076bddb5..6914fa80ae 100644 --- a/test/classes/InsertEditTest.php +++ b/test/classes/InsertEditTest.php @@ -65,6 +65,7 @@ class InsertEditTest extends TestCase $GLOBALS['cfg']['LoginCookieValidity'] = 1440; $GLOBALS['cfg']['enable_drag_drop_import'] = true; $GLOBALS['PMA_Config'] = new Config(); + $GLOBALS['PMA_Config']->set('URLQueryEncryption', false); $this->insertEdit = new InsertEdit($GLOBALS['dbi']); } diff --git a/test/classes/Navigation/NavigationTreeTest.php b/test/classes/Navigation/NavigationTreeTest.php index e1458cff05..60bb1ca318 100644 --- a/test/classes/Navigation/NavigationTreeTest.php +++ b/test/classes/Navigation/NavigationTreeTest.php @@ -13,6 +13,9 @@ use PhpMyAdmin\Config; use PhpMyAdmin\Navigation\NavigationTree; use PhpMyAdmin\Template; use PhpMyAdmin\Tests\PmaTestCase; +use PhpMyAdmin\Theme; +use PhpMyAdmin\Url; +use ReflectionMethod; /** * Tests for PhpMyAdmin\Navigation\NavigationTree class @@ -36,6 +39,7 @@ class NavigationTreeTest extends PmaTestCase { $GLOBALS['server'] = 1; $GLOBALS['PMA_Config'] = new Config(); + $GLOBALS['PMA_Config']->set('URLQueryEncryption', false); $GLOBALS['PMA_Config']->enableBc(); $GLOBALS['cfg']['Server']['host'] = 'localhost'; $GLOBALS['cfg']['Server']['user'] = 'user'; @@ -96,4 +100,37 @@ class NavigationTreeTest extends PmaTestCase $result = $this->object->renderDbSelect(); $this->assertStringContainsString('pma_navigation_select_database', $result); } + + /** + * @return void + */ + public function testEncryptQueryParams() + { + global $PMA_Config; + + $_SESSION = []; + $PMA_Config->set('URLQueryEncryption', false); + $PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32)); + + $method = new ReflectionMethod($this->object, 'encryptQueryParams'); + $method->setAccessible(true); + + $link = 'tbl_structure.php?server=1&db=test_db&table=test_table&pos=0'; + + $actual = $method->invoke($this->object, $link); + $this->assertEquals($link, $actual); + + $PMA_Config->set('URLQueryEncryption', true); + + $actual = $method->invoke($this->object, $link); + $this->assertStringStartsWith('tbl_structure.php?server=1&pos=0&eq=', $actual); + + $url = parse_url($actual); + parse_str(htmlspecialchars_decode($url['query']), $query); + + $this->assertRegExp('/^[a-zA-Z0-9-_=]+$/', $query['eq']); + $decrypted = Url::decryptQuery($query['eq']); + $this->assertJson($decrypted); + $this->assertSame('{"db":"test_db","table":"test_table"}', $decrypted); + } } diff --git a/test/classes/Plugins/Auth/AuthenticationCookieTest.php b/test/classes/Plugins/Auth/AuthenticationCookieTest.php index 0af2e45cb1..73853d75d7 100644 --- a/test/classes/Plugins/Auth/AuthenticationCookieTest.php +++ b/test/classes/Plugins/Auth/AuthenticationCookieTest.php @@ -40,6 +40,7 @@ class AuthenticationCookieTest extends PmaTestCase protected function setUp(): void { $GLOBALS['PMA_Config'] = new Config(); + $GLOBALS['PMA_Config']->set('URLQueryEncryption', false); $GLOBALS['PMA_Config']->enableBc(); $GLOBALS['server'] = 0; $GLOBALS['text_dir'] = 'ltr'; diff --git a/test/classes/PmaTestCase.php b/test/classes/PmaTestCase.php index a3b238a3ff..9f6eab36c0 100644 --- a/test/classes/PmaTestCase.php +++ b/test/classes/PmaTestCase.php @@ -40,6 +40,7 @@ class PmaTestCase extends TestCase { require ROOT_PATH . 'libraries/config.default.php'; $GLOBALS['cfg'] = $cfg; + $GLOBALS['PMA_Config']->set('URLQueryEncryption', false); } /** diff --git a/test/classes/Server/UserGroupsTest.php b/test/classes/Server/UserGroupsTest.php index d0bc43c4c3..a58255ff6a 100644 --- a/test/classes/Server/UserGroupsTest.php +++ b/test/classes/Server/UserGroupsTest.php @@ -131,7 +131,7 @@ class UserGroupsTest extends TestCase 'viewUsers' => 1, 'userGroup' => htmlspecialchars('usergroup'), ], - '' + '', false ); $this->assertStringContainsString( $url_tag, @@ -143,7 +143,7 @@ class UserGroupsTest extends TestCase 'editUserGroup' => 1, 'userGroup' => htmlspecialchars('usergroup'), ], - '' + '', false ); $this->assertStringContainsString( $url_tag, @@ -155,7 +155,7 @@ class UserGroupsTest extends TestCase 'deleteUserGroup' => 1, 'userGroup' => htmlspecialchars('usergroup'), ], - '' + '', false ); $this->assertStringContainsString( $url_tag, diff --git a/test/classes/UrlTest.php b/test/classes/UrlTest.php index b1b0138b2c..9aed8438a4 100644 --- a/test/classes/UrlTest.php +++ b/test/classes/UrlTest.php @@ -29,6 +29,7 @@ class UrlTest extends TestCase protected function setUp(): void { unset($_COOKIE['pma_lang']); + $GLOBALS['PMA_Config']->set('URLQueryEncryption', false); } /** @@ -114,4 +115,61 @@ class UrlTest extends TestCase $expected = '?server=x' . htmlentities($separator) . 'lang=en' ; $this->assertEquals($expected, Url::getCommon()); } + + /** + * @return void + */ + public function testBuildHttpQueryWithUrlQueryEncryptionDisabled() + { + global $PMA_Config; + + $PMA_Config->set('URLQueryEncryption', false); + $params = ['db' => 'test_db', 'table' => 'test_table', 'pos' => 0]; + $this->assertEquals('db=test_db&table=test_table&pos=0', Url::buildHttpQuery($params)); + } + + /** + * @return void + */ + public function testBuildHttpQueryWithUrlQueryEncryptionEnabled() + { + global $PMA_Config; + + $_SESSION = []; + $PMA_Config->set('URLQueryEncryption', true); + $PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32)); + + $params = ['db' => 'test_db', 'table' => 'test_table', 'pos' => 0]; + $query = Url::buildHttpQuery($params); + $this->assertStringStartsWith('pos=0&eq=', $query); + parse_str($query, $queryParams); + $this->assertCount(2, $queryParams); + $this->assertSame('0', $queryParams['pos']); + $this->assertTrue(is_string($queryParams['eq'])); + $this->assertNotSame('', $queryParams['eq']); + $this->assertRegExp('/^[a-zA-Z0-9-_=]+$/', $queryParams['eq']); + $decrypted = Url::decryptQuery($queryParams['eq']); + $this->assertJson($decrypted); + $this->assertSame('{"db":"test_db","table":"test_table"}', $decrypted); + } + + /** + * @return void + */ + public function testQueryEncryption() + { + global $PMA_Config; + + $_SESSION = []; + $PMA_Config->set('URLQueryEncryption', true); + $PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32)); + + $query = '{"db":"test_db","table":"test_table"}'; + $encrypted = Url::encryptQuery($query); + $this->assertNotSame($query, $encrypted); + $this->assertNotSame('', $encrypted); + $this->assertRegExp('/^[a-zA-Z0-9-_=]+$/', $encrypted); + $decrypted = Url::decryptQuery($encrypted); + $this->assertSame($query, $decrypted); + } } diff --git a/test/classes/UtilTest.php b/test/classes/UtilTest.php index f1b379d351..d5d06c9262 100644 --- a/test/classes/UtilTest.php +++ b/test/classes/UtilTest.php @@ -2700,38 +2700,22 @@ class UtilTest extends PmaTestCase { return [ [ - [ - 'index.php', - 'text', - ], + ['index.php', null, 'text'], 1000, 'text', ], [ - [ - 'index.php?some=parameter', - 'text', - ], + ['index.php', ['some' => 'parameter'], 'text'], 20, - 'text', + 'text', ], [ - [ - 'index.php', - 'text', - [], - 'target', - ], + ['index.php', null, 'text', [], 'target'], 1000, 'text', ], [ - [ - 'url.php?url=http://phpmyadmin.net/', - 'text', - [], - '_blank', - ], + ['url.php?url=http://phpmyadmin.net/', null, 'text', [], '_blank'], 1000, 'text', ],