diff --git a/web/package.json b/web/package.json index b64bbe1..a551af1 100644 --- a/web/package.json +++ b/web/package.json @@ -87,6 +87,7 @@ "snapsvg": "^0.5.1", "spectrum-colorpicker": "^1.8.0", "sprintf-js": "^1.1.1", + "tablesorter": "^2.30.6", "underscore": "^1.8.3", "underscore.string": "^3.3.4", "watchify": "~3.9.0", diff --git a/web/pgadmin/feature_tests/file_manager_test.py b/web/pgadmin/feature_tests/file_manager_test.py new file mode 100644 index 0000000..9cdce65 --- /dev/null +++ b/web/pgadmin/feature_tests/file_manager_test.py @@ -0,0 +1,164 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2018, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +from __future__ import print_function +import os +import time +import sys +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from regression.python_test_utils import test_utils +from regression.feature_utils.base_feature_test import BaseFeatureTest + + +class CheckFileManagerFeatureTest(BaseFeatureTest): + """Tests to check file manager for XSS.""" + + scenarios = [ + ("File manager feature test", + dict()) + ] + + def before(self): + connection = test_utils.get_db_connection( + self.server['db'], + self.server['username'], + self.server['db_password'], + self.server['host'], + self.server['port'] + ) + test_utils.drop_database(connection, "acceptance_test_db") + test_utils.create_database(self.server, "acceptance_test_db") + self.page.add_server(self.server) + self.wait = WebDriverWait(self.page.driver, 10) + self.XSS_FILE = '/tmp/.sql' + # Remove any previous file + if os.path.isfile(self.XSS_FILE): + os.remove(self.XSS_FILE) + + def after(self): + self.page.close_query_tool('sql', False) + self.page.remove_server(self.server) + connection = test_utils.get_db_connection( + self.server['db'], + self.server['username'], + self.server['db_password'], + self.server['host'], + self.server['port'] + ) + test_utils.drop_database(connection, "acceptance_test_db") + + def runTest(self): + print("Tests to check if File manager is vulnerable to XSS... ", + file=sys.stderr, end="") + self._navigate_to_query_tool() + self.page.fill_codemirror_area_with("SELECT 1;") + self._create_new_file() + self._open_file_manager_and_check_xss_file() + print("OK.", file=sys.stderr) + + print("File manager sorting of data", file=sys.stderr) + self._check_file_sorting() + print("OK.", file=sys.stderr) + + def _navigate_to_query_tool(self): + self.page.toggle_open_tree_item(self.server['name']) + self.page.toggle_open_tree_item('Databases') + self.page.toggle_open_tree_item('acceptance_test_db') + self.page.open_query_tool() + + def _create_new_file(self): + self.page.find_by_id("btn-save").click() + self.page.wait_for_query_tool_loading_indicator_to_disappear() + self.wait.until(EC.presence_of_element_located( + ( + By.XPATH, + "//*[contains(string(), 'Show hidden files and folders? ')]" + ) + )) + # Set the XSS value in input + self.page.find_by_id("file-input-path").clear() + self.page.find_by_id("file-input-path").send_keys( + self.XSS_FILE + ) + # Save the file + self.page.click_modal('Save') + self.page.wait_for_query_tool_loading_indicator_to_disappear() + + def _open_file_manager_and_check_xss_file(self): + self.page.find_by_id("btn-load-file").click() + self.wait.until(EC.presence_of_element_located( + ( + By.XPATH, + "//*[contains(string(), 'Show hidden files and folders? ')]" + ) + )) + self.page.find_by_id("file-input-path").clear() + self.page.find_by_id("file-input-path").send_keys( + '/tmp/' + ) + self.page.find_by_id("file-input-path").send_keys( + Keys.RETURN + ) + + if self.page.driver.capabilities['browserName'] == 'firefox': + table = self.page.wait_for_element_to_reload( + lambda driver: + driver.find_element_by_css_selector("table#contents") + ) + else: + table = self.page.driver \ + .find_element_by_css_selector("table#contents") + + contents = table.get_attribute('innerHTML') + + self.page.click_modal('Cancel') + self.page.wait_for_query_tool_loading_indicator_to_disappear() + self._check_escaped_characters( + contents, + '<img src=x onmouseover=alert("1")>.sql', + 'File manager' + ) + + def _check_escaped_characters(self, source_code, string_to_find, source): + # For XSS we need to search against element's html code + assert source_code.find( + string_to_find + ) != -1, "{0} might be vulnerable to XSS ".format(source) + + def _check_file_sorting(self): + self.page.find_by_id("btn-load-file").click() + self.wait.until( + EC.element_to_be_clickable(( + By.CSS_SELECTOR, + "#contents th[data-column='0']") + ) + ) + + # Added time.sleep so that the element to be clicked. + time.sleep(0.05) + self.page.find_by_css_selector("#contents th[data-column='0']").click() + # Check for sort Ascending + self.wait.until( + EC.presence_of_element_located(( + By.CSS_SELECTOR, + "#contents th[data-column='0'].tablesorter-headerAsc") + ) + ) + + # Click and Check for sort Descending + self.page.find_by_css_selector("#contents th[data-column='0']").click() + self.wait.until( + EC.presence_of_element_located(( + By.CSS_SELECTOR, + "#contents th[data-column='0'].tablesorter-headerDesc") + ) + ) diff --git a/web/pgadmin/feature_tests/xss_checks_file_manager_test.py b/web/pgadmin/feature_tests/xss_checks_file_manager_test.py deleted file mode 100644 index 60d7e91..0000000 --- a/web/pgadmin/feature_tests/xss_checks_file_manager_test.py +++ /dev/null @@ -1,126 +0,0 @@ -########################################################################## -# -# pgAdmin 4 - PostgreSQL Tools -# -# Copyright (C) 2013 - 2018, The pgAdmin Development Team -# This software is released under the PostgreSQL Licence -# -########################################################################## - -import os -import time -from selenium.webdriver.common.keys import Keys -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions as EC -from regression.python_test_utils import test_utils -from regression.feature_utils.base_feature_test import BaseFeatureTest - - -class CheckFileManagerFeatureTest(BaseFeatureTest): - """Tests to check file manager for XSS.""" - - scenarios = [ - ("Tests to check if File manager is vulnerable to XSS", - dict()) - ] - - def before(self): - connection = test_utils.get_db_connection( - self.server['db'], - self.server['username'], - self.server['db_password'], - self.server['host'], - self.server['port'] - ) - test_utils.drop_database(connection, "acceptance_test_db") - test_utils.create_database(self.server, "acceptance_test_db") - self.page.add_server(self.server) - self.wait = WebDriverWait(self.page.driver, 10) - self.XSS_FILE = '/tmp/.sql' - # Remove any previous file - if os.path.isfile(self.XSS_FILE): - os.remove(self.XSS_FILE) - - def after(self): - self.page.close_query_tool('sql', False) - self.page.remove_server(self.server) - connection = test_utils.get_db_connection( - self.server['db'], - self.server['username'], - self.server['db_password'], - self.server['host'], - self.server['port'] - ) - test_utils.drop_database(connection, "acceptance_test_db") - - def runTest(self): - self._navigate_to_query_tool() - self.page.fill_codemirror_area_with("SELECT 1;") - self._create_new_file() - self._open_file_manager_and_check_xss_file() - - def _navigate_to_query_tool(self): - self.page.toggle_open_tree_item(self.server['name']) - self.page.toggle_open_tree_item('Databases') - self.page.toggle_open_tree_item('acceptance_test_db') - self.page.open_query_tool() - - def _create_new_file(self): - self.page.find_by_id("btn-save").click() - self.page.wait_for_query_tool_loading_indicator_to_disappear() - self.wait.until(EC.presence_of_element_located( - ( - By.XPATH, - "//*[contains(string(), 'Show hidden files and folders? ')]" - ) - )) - # Set the XSS value in input - self.page.find_by_id("file-input-path").clear() - self.page.find_by_id("file-input-path").send_keys( - self.XSS_FILE - ) - # Save the file - self.page.click_modal('Save') - self.page.wait_for_query_tool_loading_indicator_to_disappear() - - def _open_file_manager_and_check_xss_file(self): - self.page.find_by_id("btn-load-file").click() - self.wait.until(EC.presence_of_element_located( - ( - By.XPATH, - "//*[contains(string(), 'Show hidden files and folders? ')]" - ) - )) - self.page.find_by_id("file-input-path").clear() - self.page.find_by_id("file-input-path").send_keys( - '/tmp/' - ) - self.page.find_by_id("file-input-path").send_keys( - Keys.RETURN - ) - - if self.page.driver.capabilities['browserName'] == 'firefox': - table = self.page.wait_for_element_to_reload( - lambda driver: - driver.find_element_by_css_selector("table#contents") - ) - else: - table = self.page.driver \ - .find_element_by_css_selector("table#contents") - - contents = table.get_attribute('innerHTML') - - self.page.click_modal('Cancel') - self.page.wait_for_query_tool_loading_indicator_to_disappear() - self._check_escaped_characters( - contents, - '<img src=x onmouseover=alert("1")>.sql', - 'File manager' - ) - - def _check_escaped_characters(self, source_code, string_to_find, source): - # For XSS we need to search against element's html code - assert source_code.find( - string_to_find - ) != -1, "{0} might be vulnerable to XSS ".format(source) diff --git a/web/pgadmin/misc/file_manager/static/css/file_manager.css b/web/pgadmin/misc/file_manager/static/css/file_manager.css index 6e4ee9e..6e76795 100644 --- a/web/pgadmin/misc/file_manager/static/css/file_manager.css +++ b/web/pgadmin/misc/file_manager/static/css/file_manager.css @@ -233,17 +233,6 @@ div.clip { color: #fff; } -.file_listing #contents.list th.tablesorter-headerAsc, -.file_listing #contents.list th.tablesorter-headerDesc { - background: rgb(214,212,209); /* Old browsers */ - background: -moz-linear-gradient(top, rgba(214,212,209,1) 0%, rgba(244,241,237,1) 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(214,212,209,1)), color-stop(100%,rgba(244,241,237,1))); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, rgba(214,212,209,1) 0%,rgba(244,241,237,1) 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, rgba(214,212,209,1) 0%,rgba(244,241,237,1) 100%); /* Opera 11.10+ */ - background: -ms-linear-gradient(top, rgba(214,212,209,1) 0%,rgba(244,241,237,1) 100%); /* IE10+ */ - background: linear-gradient(to bottom, rgba(214,212,209,1) 0%,rgba(244,241,237,1) 100%); /* W3C */ -} - .file_listing #contents.list td:first-child { display: table-cell; padding-left: 0; @@ -729,3 +718,43 @@ a.dz-remove { div.change_file_types span { padding-left:10px; } + +/* overall */ +.tablesorter .header, +.tablesorter .tablesorter-header { + /* black (unsorted) double arrow */ + background-image: url(data:image/gif;base64,R0lGODlhFQAJAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==); + background-repeat: no-repeat; + background-position: center right; + padding: 4px 18px 4px 4px; + white-space: normal; + cursor: pointer; +} + +.tablesorter .headerSortUp, +.tablesorter .tablesorter-headerSortUp, +.tablesorter .tablesorter-headerAsc { + /* black asc arrow */ + background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7); +} + +.tablesorter .headerSortDown, +.tablesorter .tablesorter-headerSortDown, +.tablesorter .tablesorter-headerDesc { + /* black desc arrow */ + background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7); +} + +.tablesorter thead .sorter-false { + background-image: none; + cursor: default; + padding: 4px; +} + +/* table processing indicator */ +.tablesorter .tablesorter-processing { + background-position: center center !important; + background-repeat: no-repeat !important; + background-image: url('data:image/gif;base64,R0lGODlhFAAUAKEAAO7u7lpaWgAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgACACwAAAAAFAAUAAACQZRvoIDtu1wLQUAlqKTVxqwhXIiBnDg6Y4eyx4lKW5XK7wrLeK3vbq8J2W4T4e1nMhpWrZCTt3xKZ8kgsggdJmUFACH5BAEKAAIALAcAAAALAAcAAAIUVB6ii7jajgCAuUmtovxtXnmdUAAAIfkEAQoAAgAsDQACAAcACwAAAhRUIpmHy/3gUVQAQO9NetuugCFWAAAh+QQBCgACACwNAAcABwALAAACE5QVcZjKbVo6ck2AF95m5/6BSwEAIfkEAQoAAgAsBwANAAsABwAAAhOUH3kr6QaAcSrGWe1VQl+mMUIBACH5BAEKAAIALAIADQALAAcAAAIUlICmh7ncTAgqijkruDiv7n2YUAAAIfkEAQoAAgAsAAAHAAcACwAAAhQUIGmHyedehIoqFXLKfPOAaZdWAAAh+QQFCgACACwAAAIABwALAAACFJQFcJiXb15zLYRl7cla8OtlGGgUADs=') !important; +} + diff --git a/web/pgadmin/misc/file_manager/static/js/utility.js b/web/pgadmin/misc/file_manager/static/js/utility.js index ce3cd17..ef62aba 100644 --- a/web/pgadmin/misc/file_manager/static/js/utility.js +++ b/web/pgadmin/misc/file_manager/static/js/utility.js @@ -13,6 +13,7 @@ import loading_icon from 'acitree/image/load-root.gif'; define([ 'jquery', 'underscore', 'underscore.string', 'pgadmin.alertifyjs', 'sources/gettext', 'sources/url_for', 'dropzone', 'sources/pgadmin', + 'tablesorter', ], function($, _, S, Alertify, gettext, url_for, Dropzone, pgAdmin) { /*--------------------------------------------------------- @@ -574,8 +575,8 @@ define([ result += ''; } else { - result += ''; - result += '
'; + result += ''; + result += ''; result += ''; @@ -649,8 +650,8 @@ define([ if ($('.fileinfo').data('view') == 'grid') { result += '
    '; } else { - result += '
    '; result += '' + lg.name + '' + lg.size + ''; result += '' + lg.modified + '
    '; - result += '
    ' + + result += ''; + result += ''; @@ -667,6 +668,7 @@ define([ // Add the new markup to the DOM. $('.fileinfo .file_listing').html(result); + $('.fileinfo .file_listing #contents').tablesorter(); // rename file/folder $('.file_manager button.rename').off().on('click', function(e) {
    ' + lg.name + '' + lg.size + '' + lg.modified + '