1
|
# SPDX-License-Identifier: CC0-1.0
|
2
|
|
3
|
"""
|
4
|
Haketilo unit tests - determining what to do on a given web page
|
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
|
|
26
|
csp_re = re.compile(r'^\S+\s+\S+;(?:\s+\S+\s+\S+;)*$')
|
27
|
rule_re = re.compile(r'^\s*(?P<src_kind>\S+)\s+(?P<allowed_origins>\S+)$')
|
28
|
def parse_csp(csp):
|
29
|
'''
|
30
|
Parsing of CSP string into a dict. A simplified format of CSP is assumed.
|
31
|
'''
|
32
|
assert csp_re.match(csp)
|
33
|
|
34
|
result = {}
|
35
|
|
36
|
for rule in csp.split(';')[:-1]:
|
37
|
match = rule_re.match(rule)
|
38
|
result[match.group('src_kind')] = match.group('allowed_origins')
|
39
|
|
40
|
return result
|
41
|
|
42
|
@pytest.mark.get_page('https://gotmyowndoma.in')
|
43
|
def test_decide_policy(execute_in_page):
|
44
|
"""
|
45
|
policy.js contains code that, using a Pattern Query Tree instance and a URL,
|
46
|
decides what Haketilo should do on a page opened at that URL, i.e. whether
|
47
|
it should block or allow script execution and whether it should inject its
|
48
|
own scripts and which ones. Test that the policy object gets constructed
|
49
|
properly.
|
50
|
"""
|
51
|
execute_in_page(load_script('common/policy.js'))
|
52
|
|
53
|
policy = execute_in_page(
|
54
|
'''
|
55
|
returnval(decide_policy(pqt.make(), "http://unkno.wn/", true, "abcd"));
|
56
|
''')
|
57
|
assert policy['allow'] == True
|
58
|
for prop in ('mapping', 'payload', 'nonce', 'csp', 'error'):
|
59
|
assert prop not in policy
|
60
|
|
61
|
policy = execute_in_page(
|
62
|
'''{
|
63
|
const tree = pqt.make();
|
64
|
pqt.register(tree, "http://kno.wn", "~allow", 1);
|
65
|
returnval(decide_policy(tree, "http://kno.wn/", false, "abcd"));
|
66
|
}''')
|
67
|
assert policy['allow'] == True
|
68
|
assert policy['mapping'] == '~allow'
|
69
|
for prop in ('payload', 'nonce', 'csp', 'error'):
|
70
|
assert prop not in policy
|
71
|
|
72
|
policy = execute_in_page(
|
73
|
'''
|
74
|
returnval(decide_policy(pqt.make(), "http://unkno.wn/", false, "abcd"));
|
75
|
'''
|
76
|
)
|
77
|
assert policy['allow'] == False
|
78
|
for prop in ('mapping', 'payload', 'nonce', 'error'):
|
79
|
assert prop not in policy
|
80
|
assert parse_csp(policy['csp']) == {
|
81
|
'prefetch-src': "'none'",
|
82
|
'script-src-attr': "'none'",
|
83
|
'script-src': "'none'",
|
84
|
'script-src-elem': "'none'"
|
85
|
}
|
86
|
|
87
|
policy = execute_in_page(
|
88
|
'''{
|
89
|
const tree = pqt.make();
|
90
|
pqt.register(tree, "http://kno.wn", "~allow", 0);
|
91
|
returnval(decide_policy(tree, "http://kno.wn/", true, "abcd"));
|
92
|
}''')
|
93
|
assert policy['allow'] == False
|
94
|
assert policy['mapping'] == '~allow'
|
95
|
for prop in ('payload', 'nonce', 'error'):
|
96
|
assert prop not in policy
|
97
|
assert parse_csp(policy['csp']) == {
|
98
|
'prefetch-src': "'none'",
|
99
|
'script-src-attr': "'none'",
|
100
|
'script-src': "'none'",
|
101
|
'script-src-elem': "'none'"
|
102
|
}
|
103
|
|
104
|
policy = execute_in_page(
|
105
|
'''{
|
106
|
const tree = pqt.make();
|
107
|
pqt.register(tree, "http://kno.wn", "m1", {identifier: "res1"});
|
108
|
returnval(decide_policy(tree, "http://kno.wn/", true, "abcd"));
|
109
|
}''')
|
110
|
assert policy['allow'] == False
|
111
|
assert policy['mapping'] == 'm1'
|
112
|
assert policy['payload'] == {'identifier': 'res1'}
|
113
|
assert 'error' not in policy
|
114
|
assert policy['nonce'] == \
|
115
|
sha256('m1:res1:http://kno.wn/:abcd'.encode()).digest().hex()
|
116
|
assert parse_csp(policy['csp']) == {
|
117
|
'prefetch-src': f"'none'",
|
118
|
'script-src-attr': f"'none'",
|
119
|
'script-src': f"'nonce-{policy['nonce']}'",
|
120
|
'script-src-elem': f"'nonce-{policy['nonce']}'"
|
121
|
}
|
122
|
|
123
|
policy = execute_in_page(
|
124
|
'returnval(decide_policy(pqt.make(), "<bad_url>", true, "abcd"));'
|
125
|
)
|
126
|
assert policy['allow'] == False
|
127
|
assert policy['error'] == {'haketilo_error_type': 'deciding_policy'}
|
128
|
for prop in ('mapping', 'payload', 'nonce'):
|
129
|
assert prop not in policy
|
130
|
assert parse_csp(policy['csp']) == {
|
131
|
'prefetch-src': "'none'",
|
132
|
'script-src-attr': "'none'",
|
133
|
'script-src': "'none'",
|
134
|
'script-src-elem': "'none'"
|
135
|
}
|