Project

General

Profile

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

haketilo / test / haketilo_test / unit / test_policy_deciding.py @ f8dedf60

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
    }
(18-18/26)