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