From 0d51c8d10ba494c123951420e1dbff059c006a84 Mon Sep 17 00:00:00 2001 From: Jon Lockwood Date: Thu, 27 Jan 2022 13:12:19 +0930 Subject: [PATCH] test(privacy): Created a test to check external requests during page load This test checks if the external request is within the apporved list. This test will prevent the adding of external urls within the page load. MR !2 --- test/requirements.txt | 3 ++ test/unit/conftest.py | 105 ++++++++++++++++++++++++++++++++++++++ test/unit/privacy_test.py | 38 ++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 test/requirements.txt create mode 100644 test/unit/conftest.py create mode 100644 test/unit/privacy_test.py diff --git a/test/requirements.txt b/test/requirements.txt new file mode 100644 index 0000000..c4188f7 --- /dev/null +++ b/test/requirements.txt @@ -0,0 +1,3 @@ +selenium==3.141.0 + +pytest==6.2.5 diff --git a/test/unit/conftest.py b/test/unit/conftest.py new file mode 100644 index 0000000..7f1798a --- /dev/null +++ b/test/unit/conftest.py @@ -0,0 +1,105 @@ +import hashlib +import json +import os +import re + +import pytest + +from selenium import webdriver +from selenium.webdriver.chrome.options import Options + +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + +class Data: + + def process_browser_log_entry(self, entry): + response = json.loads(entry['message'])['message'] + return response + + def __init__(self): + + caps = DesiredCapabilities.CHROME + caps['goog:loggingPrefs'] = {'performance': 'ALL'} + + + chrome_options = Options() + chrome_options.add_argument("no-sandbox") + chrome_options.add_argument("headless") + chrome_options.add_argument("start-maximized") + chrome_options.add_argument("window-size=1900,1080"); + + self.driver = webdriver.Chrome(desired_capabilities=caps, options=chrome_options) + + self.urls = [] + self.suffux_path = os.path.realpath('./build') + self.urls += [os.path.join(dp, f) for dp, dn, fn in os.walk(os.path.expanduser('./build')) for f in fn if f.endswith('.html')] + + + data = { + 'page_load_resource_links': {}, + 'source_files': [], + 'hyperlinks': {} + } + + + for check_file in self.urls: + + check_url = 'file://' + self.suffux_path + check_file.replace('./build','') + source_file = check_file.replace('./build','')[1:] + + if source_file not in data['source_files']: + data['source_files'].append(source_file) + + self.driver.get(check_url) + + events = [self.process_browser_log_entry(entry) for entry in self.driver.get_log('performance')] + + for entry in events: + + if entry['method'] == 'Network.requestWillBeSent': + + http_status = str([response['params']['response']['status'] for response in events if response['method'] == 'Network.responseReceived' and response['params']['requestId'] == entry['params']['requestId']]).replace('[', '').replace(']', '') + + url = str(entry['params']['request']['url']) + + + url_id = hashlib.md5(bytes(url, 'utf-8')).hexdigest() + + + if re.match("^http|file.*", url) is not None: + + source_file_line_number = '' + + if 'lineNumber' in entry['params']['initiator']: + + source_file_line_number = str(entry['params']['initiator']['lineNumber']) + + request_protocol = re.match("^[http|file]+s?", url).group(0) + + if re.match("^http.*", url) is not None: + + domain = re.match(r'^([a-z]+[\.|a-z|]+)',url.replace(request_protocol + '://', '')).group(0) + + request_path = url.replace(request_protocol + '://','').replace(domain, '')[1:] + + + elif re.match("^file.*", url) is not None: + + domain = 'file' + + request_path = url.replace(request_protocol + '://','')[1:] + + if url_id in data['page_load_resource_links']: + + data['page_load_resource_links'][url_id]['source_files'].append({'name': source_file, 'line_number': source_file_line_number, 'http_status': http_status}) + + else: + + data['page_load_resource_links'][url_id] = {'url': url, 'request_protocol': request_protocol, 'domain': domain, 'request_path': request_path, 'source_files': [ {'name': source_file, 'line_number': source_file_line_number, 'http_status': http_status} ]} + + self.test_data = data + + +print("\n"+'Creating test data') +print("\n\ntest data:\n" + json.dumps(Data().test_data, indent=2, default=str)) + diff --git a/test/unit/privacy_test.py b/test/unit/privacy_test.py new file mode 100644 index 0000000..4fd5997 --- /dev/null +++ b/test/unit/privacy_test.py @@ -0,0 +1,38 @@ +import pytest + +from conftest import Data + +class Test: + + data = Data() + + def setup_method(self): + + self.approved_external_requests = { + 'gitlab.com': [ + 'api/v4/projects/nofusscomputing%2Finfrastructure%2Fwebsite', + 'uploads/-/system/user/avatar/4125177/avatar.png' + ] + } + + + @pytest.mark.parametrize( + argnames='data', + argvalues=[link for url_id, link in data.test_data['page_load_resource_links'].items() if link['request_protocol'][0:4] =='http'], + ids=[url_id for url_id, link in data.test_data['page_load_resource_links'].items() if link['request_protocol'][0:4] =='http'] + ) + def test_page_external_requests(self, data): + + check_url = data['url'] + + assert data['request_protocol'] == 'https', f"Insecure Request to domain [{data['request_path']}] in source files [{data['source_files']}]" + + assert data['domain'] in self.approved_external_requests, f"A request is being made to a non-approved domain [{data['domain']}] path [{data['request_path']}] in source files [{data['source_files']}]" + + assert data['request_path'] in self.approved_external_requests[data['domain']], f"A request is being made to a non-approved path [{data['request_path']}] on domain [{data['domain']}] in source files [{data['source_files']}]" + + + + def teardown_method(self): + pass +