Project

General

Profile

Download (5.68 KB) Statistics
| Branch: | Tag: | Revision:

haketilo / test / haketilo_test / unit / test_webrequest.py @ aec5c9ae

1
# SPDX-License-Identifier: CC0-1.0
2

    
3
"""
4
Haketilo unit tests - modifying requests using webRequest API
5
"""
6

    
7
# This file is part of Haketilo
8
#
9
# Copyright (C) 2021, Wojtek Kosior <koszko@koszko.org>
10
#
11
# This program is free software: you can redistribute it and/or modify
12
# it under the terms of the CC0 1.0 Universal License as published by
13
# the Creative Commons Corporation.
14
#
15
# This program is distributed in the hope that it will be useful,
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
# CC0 1.0 Universal License for more details.
19

    
20
import re
21
from hashlib import sha256
22
import pytest
23

    
24
from ..script_loader import load_script
25
from .utils import are_scripts_allowed
26

    
27
allowed_url = 'https://site.with.scripts.allow.ed/'
28
blocked_url = 'https://site.with.scripts.block.ed/'
29
payload_url = 'https://site.with.paylo.ad/'
30

    
31
def webrequest_js():
32
    return (load_script('background/webrequest.js',
33
                        '#IMPORT common/patterns_query_tree.js AS pqt') +
34
            ''';
35
            // Mock pattern tree.
36
            tree = pqt.make();
37
            // Mock default allow.
38
            default_allow = {name: "default_allow", value: true};
39

    
40
            // Rule to block scripts.
41
            pqt.register(tree, "%(blocked)s***",
42
                         "~allow", 0);
43

    
44
            // Rule to allow scripts, but overridden by payload assignment.
45
            pqt.register(tree, "%(payload)s***", "~allow", 1);
46
            pqt.register(tree, "%(payload)s***", "somemapping",
47
                         {identifier: "someresource"});
48

    
49
            // Mock stream_filter.
50
            stream_filter.apply = (details, headers, policy) => headers;
51
            ''' % {'blocked': blocked_url, 'payload': payload_url})
52

    
53
def webrequest_js_start_called():
54
    return webrequest_js() + ';\nstart("somesecret");'
55

    
56
ext_url = 'moz-extension://49de6ce9-49fc-49e1-8102-7ef35286389c/html/settings.html'
57
prefix = 'X-Haketilo-' + sha256(ext_url.encode()).digest().hex()
58

    
59
# Prepare a list of headers as could be sent by a website.
60
sample_csp_header = {
61
    'name': 'Content-Security-Policy',
62
    'value': "script-src 'self';"
63
}
64
sample_csp_header_idx = 7
65

    
66
sample_headers = [
67
    {'name': 'Content-Type',     'value': 'text/html;charset=utf-8'},
68
    {'name': 'Content-Length',   'value': '61954'},
69
    {'name': 'Content-Language', 'value': 'en'},
70
    {'name': 'Expires',          'value': 'Mon, 12 Mar 2012 11:04...'},
71
    {'name': 'Last-Modified',    'value': 'Fri, 26 Jul 2013 22:50...'},
72
    {'name': 'Cache-Control',    'value': 'max-age=0, s-maxage=86...'},
73
    {'name': 'Age',              'value': '224'},
74
    {'name': 'Server',           'value': 'nginx/1.1.19'},
75
    {'name': 'Date',             'value': 'Thu, 10 Mar 2022 12:09...'}
76
]
77

    
78
sample_headers.insert(sample_csp_header_idx, sample_csp_header)
79

    
80
# Prepare a list of headers as would be crafted by Haketilo when there is a
81
# payload to inject.
82
nonce_source = f'somemapping:someresource:{payload_url}:somesecret'.encode()
83
nonce = f'nonce-{sha256(nonce_source).digest().hex()}'
84

    
85
payload_csp_header = {
86
    'name': f'Content-Security-Policy',
87
    'value': ("prefetch-src 'none'; script-src-attr 'none'; "
88
              f"script-src '{nonce}' 'unsafe-eval'; script-src-elem '{nonce}';")
89
}
90

    
91
sample_payload_headers = [
92
    *sample_headers,
93
    {'name': prefix, 'value': ':)'},
94
    payload_csp_header
95
]
96

    
97
sample_payload_headers[sample_csp_header_idx] = {
98
    **sample_csp_header,
99
    'name': f'{prefix}-{sample_csp_header["name"]}',
100
}
101

    
102
# Prepare a list of headers as would be crafted by Haketilo when scripts are
103
# blocked.
104
sample_blocked_headers = [*sample_payload_headers]
105
sample_blocked_headers.pop()
106
sample_blocked_headers.append(sample_csp_header)
107
sample_blocked_headers.append({
108
    'name': f'Content-Security-Policy',
109
    'value': ("prefetch-src 'none'; script-src-attr 'none'; "
110
              "script-src 'none' 'unsafe-eval'; script-src-elem 'none';")
111
})
112

    
113
@pytest.mark.get_page('https://gotmyowndoma.in')
114
@pytest.mark.parametrize('params', [
115
    (sample_headers,         allowed_url),
116
    (sample_blocked_headers, blocked_url),
117
    (sample_payload_headers, payload_url),
118
])
119
def test_webrequest_on_headers_received(driver, execute_in_page, params):
120
    """Unit-test the on_headers_received() function."""
121
    headers_out, url = params
122

    
123
    execute_in_page(
124
        '''{
125
        // Mock browser object.
126
        const url = arguments[0];
127
        this.browser = {runtime: {getURL: () => url}};
128
        }''',
129
        ext_url)
130

    
131
    execute_in_page(webrequest_js())
132

    
133
    execute_in_page('secret = "somesecret";')
134

    
135
    for headers_in in [
136
            sample_headers,
137
            sample_blocked_headers,
138
            sample_payload_headers
139
    ]:
140
        details = {'url': url, 'responseHeaders': headers_in, 'fromCache': True}
141
        res = execute_in_page('returnval(on_headers_received(arguments[0]));',
142
                              details)
143

    
144
        assert res == {'responseHeaders': headers_out}
145

    
146
@pytest.mark.ext_data({'background_script': webrequest_js_start_called})
147
@pytest.mark.usefixtures('webextension')
148
def test_webrequest_real_pages(driver, execute_in_page):
149
    """
150
    Test webRequest-based header modifications by loading actual pages and
151
    attempting to run scripts within them.
152
    """
153
    for attempt in range(10):
154
        driver.get('https://site.with.scripts.block.ed/')
155

    
156
        if not are_scripts_allowed(driver):
157
            break
158
        assert attempt != 9
159

    
160
    driver.get(allowed_url)
161
    assert are_scripts_allowed(driver)
162

    
163
    driver.get(payload_url)
164
    assert not are_scripts_allowed(driver)
165
    source = 'somemapping:someresource:https://site.with.paylo.ad/index.html:somesecret'
166
    assert are_scripts_allowed(driver, sha256(source.encode()).digest().hex())
(25-25/26)