Project

General

Profile

« Previous | Next » 

Revision fbfddb02

Added by koszko over 1 year ago

add actual payload injection functionality to new content script

View differences:

content/content.js
48 48
#FROM common/policy.js            IMPORT decide_policy
49 49
#FROM content/policy_enforcing.js IMPORT enforce_blocking
50 50

  
51
let already_run = false, page_info;
51
let already_run = false, resolve_page_info,
52
    page_info_prom = new Promise(cb => resolve_page_info = cb);
52 53

  
53 54
function on_page_info_request([type], sender, respond_cb) {
54 55
    if (type !== "page_info")
55 56
	return;
56 57

  
57
    respond_cb(page_info);
58
    page_info_prom.then(respond_cb);
59

  
60
    return true;
58 61
}
59 62

  
60
globalThis.haketilo_content_script_main = function() {
63
globalThis.haketilo_content_script_main = async function() {
61 64
    if (already_run)
62 65
        return;
63 66

  
......
73 76
				 document.URL,
74 77
				 globalThis.haketilo_defualt_allow,
75 78
				 globalThis.haketilo_secret);
76
    page_info = Object.assign({url: document.URL}, policy);
79
    const page_info = Object.assign({url: document.URL}, policy);
77 80
    ["csp", "nonce"].forEach(prop => delete page_info[prop]);
78 81

  
79
    enforce_blocking(policy);
82
    if ("payload" in policy) {
83
	const msg = ["indexeddb_files", policy.payload.identifier];
84
	var scripts_prom = browser.runtime.sendMessage(msg);
85
    }
86

  
87
    await enforce_blocking(policy);
88

  
89
    if ("payload" in policy) {
90
	const script_response = await scripts_prom;
91

  
92
	if ("error" in script_response) {
93
	    resolve_page_info(Object.assign(page_info, script_response));
94
	    return;
95
	} else {
96
	    for (const script_contents of script_response) {
97
		const html_ns = "http://www.w3.org/1999/xhtml";
98
		const script = document.createElementNS(html_ns, "script");
99

  
100
		script.innerText = script_contents;
101
		script.setAttribute("nonce", policy.nonce);
102
		document.documentElement.append(script);
103
		script.remove();
104
	    }
105
	}
106
    }
107

  
108
    resolve_page_info(page_info);
80 109
}
81 110

  
82 111
function main() {
test/unit/test_content.py
42 42
content_script = \
43 43
    '''
44 44
    /* Mock dynamic content script - case 'before'. */
45
    if (/#dynamic_before$/.test(document.URL)) {
45
    if (/dynamic_before/.test(document.URL)) {
46 46
        %s;
47 47
    }
48 48

  
......
50 50
    %s;
51 51

  
52 52
    /* Rest of mocks */
53

  
54
    function mock_decide_policy() {
55
        nonce = "12345";
56
        return {
57
            allow: false,
58
            mapping: "what-is-programmers-favorite-drinking-place",
59
            payload: {identifier: "foo-bar"},
60
            nonce,
61
            csp: "prefetch-src 'none'; script-src-attr 'none'; script-src 'nonce-12345'; script-src-elem 'nonce-12345';"
62
        };
63
    }
64

  
65
    async function mock_payload_error([type, res_id]) {
66
        if (type === "indexeddb_files")
67
            return {error: {haketilo_error_type: "missing", id: res_id}};
68
    }
69

  
70
    async function mock_payload_ok([type, res_id]) {
71
        if (type === "indexeddb_files")
72
            return [1, 2].map(n => `window.haketilo_injected_${n} = ${n}${n};`);
73
    }
74

  
75
    if (/payload_error/.test(document.URL)) {
76
        browser.runtime.sendMessage = mock_payload_error;
77
        decide_policy = mock_decide_policy;
78
    } else if (/payload_ok/.test(document.URL)) {
79
        browser.runtime.sendMessage = mock_payload_ok;
80
        decide_policy = mock_decide_policy;
81
    }
82
    /* Otherwise, script blocking policy without payload to inject is used. */
83

  
53 84
    const data_to_verify = {};
54 85
    function data_set(prop, val) {
55 86
        data_to_verify[prop] = val;
......
61 92
    enforce_blocking = policy => data_set("enforcing", policy);
62 93

  
63 94
    browser.runtime.onMessage.addListener = async function (listener_cb) {
64
        await new Promise(cb => setTimeout(cb, 0));
95
        await new Promise(cb => setTimeout(cb, 10));
65 96

  
66 97
        /* Mock a good request. */
67 98
        const set_good = val => data_set("good_request_result", val);
68
        listener_cb(["page_info"], {}, val => set_good(val));
99
        data_set("good_request_returned",
100
                 !!listener_cb(["page_info"], {}, val => set_good(val)));
69 101

  
70 102
        /* Mock a bad request. */
71 103
        const set_bad = val => data_set("bad_request_result", val);
72
        listener_cb(["???"], {}, val => set_bad(val));
104
        data_set("bad_request_returned",
105
                 !!listener_cb(["???"], {}, val => set_bad(val)));
73 106
    }
74 107

  
75 108
    /* main() call - normally present in content.js, inside '#IF !UNIT_TEST'. */
76 109
    main();
77 110

  
78 111
    /* Mock dynamic content script - case 'after'. */
79
    if (/#dynamic_after$/.test(document.URL)) {
112
    if (/#dynamic_after/.test(document.URL)) {
80 113
        %s;
81 114
    }
82 115

  
......
85 118

  
86 119
@pytest.mark.ext_data({'content_script': content_script})
87 120
@pytest.mark.usefixtures('webextension')
88
@pytest.mark.parametrize('target', ['dynamic_before', 'dynamic_after'])
89
def test_content_unprivileged_page(driver, execute_in_page, target):
121
@pytest.mark.parametrize('target1', ['dynamic_before'])#, 'dynamic_after'])
122
@pytest.mark.parametrize('target2', [
123
    'scripts_blocked',
124
    'payload_error',
125
    'payload_ok'
126
])
127
def test_content_unprivileged_page(driver, execute_in_page, target1, target2):
90 128
    """
91 129
    Test functioning of content.js on an page using unprivileged schema (e.g.
92 130
    'https://' and not 'about:').
93 131
    """
94
    driver.get(f'https://gotmyowndoma.in/index.html#{target}')
95
    data = json.loads(driver.execute_script('return window.data_to_verify;'))
132
    driver.get(f'https://gotmyowndoma.in/index.html#{target1}-{target2}')
133

  
134
    def get_data(driver):
135
        data = driver.execute_script('return window.data_to_verify;')
136
        return data if 'good_request_result' in data else False
137

  
138
    data = json.loads(WebDriverWait(driver, 10).until(get_data))
96 139

  
97 140
    assert 'gotmyowndoma.in' in data['good_request_result']['url']
98 141
    assert 'bad_request_result' not in data
99 142

  
143
    assert data['good_request_returned'] == True
144
    assert data['bad_request_returned'] == False
145

  
100 146
    assert data['cacher_started'] == True
101 147

  
102
    assert data['enforcing']['allow']  == False
103
    assert 'mapping' not in data['enforcing']
104
    assert 'error'   not in data['enforcing']
148
    for obj in (data['good_request_result'], data['enforcing']):
149
        assert obj['allow'] == False
150

  
151
    assert 'error' not in data['enforcing']
152

  
153
    if target2.startswith('payload'):
154
        for obj in (data['good_request_result'], data['enforcing']):
155
            assert obj['payload']['identifier'] == 'foo-bar'
156
            assert 'mapping' in obj
157
    else:
158
        assert 'payload' not in data['enforcing']
159
        assert 'mapping' not in data['enforcing']
105 160

  
106 161
    assert data['script_run_without_errors'] == True
107 162

  
163
    def vars_made_by_payload(driver):
164
        vars_values = driver.execute_script(
165
            'return [1, 2].map(n => window[`haketilo_injected_${n}`]);'
166
        )
167
        if vars_values != [None, None]:
168
            return vars_values
169

  
170
    if target2 == 'payload_error':
171
        assert data['good_request_result']['error'] == {
172
            'haketilo_error_type': 'missing',
173
            'id': 'foo-bar'
174
        }
175
    elif target2 == 'payload_ok':
176
        vars_values = WebDriverWait(driver, 10).until(vars_made_by_payload)
177
        assert vars_values == [11, 22]
178

  
108 179
@pytest.mark.ext_data({'content_script': content_script})
109 180
@pytest.mark.usefixtures('webextension')
110 181
@pytest.mark.parametrize('target', ['dynamic_before', 'dynamic_after'])

Also available in: Unified diff