File: dnt_test.py

package info (click to toggle)
privacybadger 2026.2.20-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,040 kB
  • sloc: javascript: 56,619; python: 2,214; sh: 406; makefile: 57; xml: 6
file content (318 lines) | stat: -rw-r--r-- 13,647 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
#!/usr/bin/env python

import json
import unittest

import pytest

import pbtest

from functools import partial

from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By

from pbtest import retry_until


class DntTest(pbtest.PBSeleniumTest):
    """Tests to make sure DNT policy checking works as expected."""

    def setUp(self):
        self.FIXTURE_DOMAIN = "efforg.github.io"
        self.FIXTURE_PARENT_DOMAIN = "github.io"
        self.FIXTURE_URL = (
            f"https://{self.FIXTURE_DOMAIN}/privacybadger-test-fixtures/html/")
        # TODO switch to scripting in Firefox (MV2) to remove delay
        # TODO https://github.com/EFForg/privacybadger/issues/2948
        self.FIXTURE_URL += "navigator_donottrack_delayed.html"

    def get_first_party_headers(self, url):
        self.load_url(url)

        text = self.driver.find_element(By.TAG_NAME, 'body').text

        try:
            # work around MS Edge JSON viewer garbage
            if text.startswith('1\n'):
                text = '{' + text.partition('{')[2]
            headers = json.loads(text)['headers']
        except ValueError:
            print(f"\nFailed to parse JSON from {repr(text)}")
            return None

        return headers

    def set_dnt_hashes(self):
        # MEGAHACK: make sha1 of "cookies=0" a valid DNT hash
        # so that the DNT policy checks to the domain that replies with cookies=X
        # will succeed when we don't send cookies along with the request
        self.load_url(self.options_url)
        self.driver.execute_async_script(
            "let done = arguments[arguments.length - 1];"
            "chrome.runtime.sendMessage({"
            "  type: 'setDntHashes',"
            "  value: { 'cookies=0 test policy': 'f63ee614ebd77f8634b92633c6bb809a64b9a3d7' }"
            "}, done);")

    def assert_navigator_gpc_unset(self, msg=""):
        # GPC on Navigator should be unset (Chrome) or False (Firefox)
        assert self.js("""
return (typeof navigator.globalPrivacyControl == 'undefined' ||
  navigator.globalPrivacyControl === false);"""), msg

    def test_dnt_policy_check_should_happen_for_blocked_domains(self):
        PAGE_URL = (
            "https://efforg.github.io/privacybadger-test-fixtures/html/"
            "recording_nontracking_domains.html"
        )
        DNT_DOMAIN = "dnt-request-cookies-test.trackersimulator.org"

        self.clear_tracker_data()

        self.set_dnt_hashes()

        # mark the "DNT-compliant" domain for blocking
        self.block_domain(DNT_DOMAIN)

        # visit a page that loads a resource from that domain
        self.load_url(PAGE_URL)

        # verify that the domain is blocked
        self.open_popup(PAGE_URL)
        assert self.get_domain_slider_state(DNT_DOMAIN) == "block", (
            "DNT-compliant resource should have been blocked at first")

        def reload_and_see_if_unblocked():
            # switch back to the page with the DNT-compliant resource
            self.switch_to_window_with_url(PAGE_URL)

            # reload it
            self.load_url(PAGE_URL)

            self.open_popup(PAGE_URL)
            return self.get_domain_slider_state(DNT_DOMAIN) == "block"

        # verify that the domain is allowed
        was_blocked = retry_until(
            reload_and_see_if_unblocked,
            tester=lambda x: not x,
            msg="Waiting a bit for DNT check to complete and retrying ...")

        assert not was_blocked, (
            "DNT-compliant resource should have gotten unblocked")

    def test_dnt_policy_check_should_not_set_cookies(self):
        TEST_DOMAIN = "dnt-test.trackersimulator.org"
        TEST_URL = f"https://{TEST_DOMAIN}/"

        # verify that the domain itself doesn't set cookies
        self.load_url(TEST_URL)
        assert not self.driver.get_cookies(), "Expect no cookies at first"

        # directly visit a DNT policy URL known to set cookies
        self.load_url(TEST_URL + ".well-known/dnt-policy.txt")
        assert len(self.driver.get_cookies()) == 1, (
            "DNT policy URL should have set a cookie")

        # verify we got a cookie
        self.load_url(TEST_URL)
        assert len(self.driver.get_cookies()) == 1, "Should still have one cookie"

        # clear cookies and verify
        self.driver.delete_all_cookies()
        self.load_url(TEST_URL)
        assert not self.driver.get_cookies(), "Should have no cookies again"

        # perform a DNT policy check
        self.check_dnt(TEST_DOMAIN)

        # check that we didn't get cookied by the DNT URL
        self.load_url(TEST_URL)
        assert not self.driver.get_cookies(), (
            "Shouldn't have any cookies after the DNT check")

    def test_dnt_policy_check_should_not_send_cookies(self):
        TEST_DOMAIN = "dnt-request-cookies-test.trackersimulator.org"
        TEST_URL = f"https://{TEST_DOMAIN}/"

        # directly visit a DNT policy URL known to set cookies
        self.load_url(TEST_URL + ".well-known/dnt-policy.txt")
        assert len(self.driver.get_cookies()) == 1, (
            "DNT policy URL should have set a cookie")

        # how to check we didn't send a cookie along with request?
        # the DNT policy URL used by this test returns "cookies=X"
        # where X is the number of cookies it got
        self.set_dnt_hashes()

        # perform a DNT policy check
        result = self.check_dnt(TEST_DOMAIN)
        assert result, "One or more cookies were sent (cookies=0 policy hash did not match)"

    @pytest.mark.flaky(reruns=3, condition=pbtest.shim.browser_type in ("chrome", "edge"))
    def test_should_not_record_nontracking_domains(self):
        NONTRACKING_FIXTURE_URL = (
            "https://efforg.github.io/privacybadger-test-fixtures/html/"
            "recording_nontracking_domains.html"
        )
        TRACKING_DOMAIN = "dnt-request-cookies-test.trackersimulator.org"
        NON_TRACKING_DOMAIN = "www.eff.org"

        # clear pre-trained/seed tracker data
        self.clear_tracker_data()

        # enable local learning
        self.wait_for_script("return window.OPTIONS_INITIALIZED")
        self.find_el_by_css('a[href="#tab-general-settings"]').click()
        self.find_el_by_css('#local-learning-checkbox').click()

        # visit a page containing two third-party resources,
        # one from a cookie-tracking domain
        # and one from a non-tracking domain
        self.load_url(NONTRACKING_FIXTURE_URL)

        # verify both domains are present on the page
        try:
            selector = f"iframe[src*='{TRACKING_DOMAIN}']"
            self.driver.find_element(By.CSS_SELECTOR, selector)
        except NoSuchElementException:
            self.fail("Unable to find the tracking domain on the page")
        try:
            selector = f"img[src*='{NON_TRACKING_DOMAIN}']"
            self.driver.find_element(By.CSS_SELECTOR, selector)
        except NoSuchElementException:
            self.fail("Unable to find the non-tracking domain on the page")

        action_map = self.get_badger_storage('action_map')

        # verify that the cookie-tracking domain was recorded
        assert TRACKING_DOMAIN in action_map, (
            "Tracking domain should have gotten recorded")

        # verify that the non-tracking domain was not recorded
        assert NON_TRACKING_DOMAIN not in action_map, (
            "Non-tracking domain should not have gotten recorded")

    def test_first_party_dnt_header(self):
        TEST_URL = "https://httpbingo.org/get"
        headers = retry_until(partial(self.get_first_party_headers, TEST_URL),
                              times=8)
        assert headers is not None, "It seems we failed to get headers"
        assert 'Dnt' in headers, "DNT header should have been present"
        assert 'Sec-Gpc' in headers, "GPC header should have been present"
        assert headers['Dnt'] == ["1"], 'DNT header should have been set to "1"'
        assert headers['Sec-Gpc'] == ["1"], 'Sec-Gpc header should have been set to "1"'

    def test_no_dnt_header_when_disabled_on_site(self):
        TEST_URL = "https://httpbingo.org/get"
        self.disable_badger_on_site(TEST_URL)
        headers = retry_until(partial(self.get_first_party_headers, TEST_URL),
                              times=8)
        assert headers is not None, "It seems we failed to get headers"
        assert 'Dnt' not in headers, "DNT header should have been missing"
        assert 'Sec-Gpc' not in headers, "GPC header should have been missing"

    def test_no_dnt_header_when_dnt_disabled(self):
        TEST_URL = "https://httpbingo.org/get"

        self.load_url(self.options_url)
        self.wait_for_script("return window.OPTIONS_INITIALIZED")
        self.find_el_by_css('a[href="#tab-general-settings"]').click()
        self.find_el_by_css('#enable_dnt_checkbox').click()

        headers = retry_until(partial(self.get_first_party_headers, TEST_URL),
                              times=8)
        assert headers is not None, "It seems we failed to get headers"
        assert 'Dnt' not in headers, "DNT header should have been missing"
        assert 'Sec-Gpc' not in headers, "GPC header should have been missing"

    def test_navigator_object(self):
        self.load_url(self.FIXTURE_URL, wait_for_body_text=True)
        body_text = self.driver.find_element(By.TAG_NAME, 'body').text
        assert body_text == 'no tracking (navigator.doNotTrack="1")', (
            'navigator.doNotTrack should have been set to "1"')
        assert self.js("return navigator.globalPrivacyControl === true"), (
            "navigator.globalPrivacyControl should have been set to true")
        assert self.js("""
return Object.getOwnPropertyDescriptor(
  Navigator.prototype, 'globalPrivacyControl')?.get?.call(navigator);
"""), "GPC should be set on Navigator.prototype"

    def test_navigator_unmodified_when_disabled_on_site(self):
        self.disable_badger_on_site(self.FIXTURE_URL)

        self.load_url(self.FIXTURE_URL, wait_for_body_text=True)

        # navigator.doNotTrack defaults to null in Chrome, "unspecified" in Firefox
        body_text = self.driver.find_element(By.TAG_NAME, 'body').text
        assert body_text[0:5] == 'unset', (
            "navigator.doNotTrack should be unset or \"unspecified\"")

        self.assert_navigator_gpc_unset("navigator.globalPrivacyControl should be unset or False")

    def test_navigator_disabling_on_site_parent_domain(self):
        """Needs to be consistent with test_disabling_on_site_parent_domain()"""
        self.disable_badger_on_site(self.FIXTURE_PARENT_DOMAIN)
        self.load_url(self.FIXTURE_URL, wait_for_body_text=True)
        self.assert_navigator_gpc_unset("navigator.globalPrivacyControl should be unset or False")

    def test_navigator_disabling_on_site_wildcard(self):
        """Needs to be consistent with test_disabling_on_site_wildcard()"""
        self.disable_badger_on_site("*." + self.FIXTURE_PARENT_DOMAIN)
        self.load_url(self.FIXTURE_URL, wait_for_body_text=True)
        self.assert_navigator_gpc_unset("navigator.globalPrivacyControl should be unset or False")

    def test_navigator_unmodified_when_dnt_disabled(self):
        self.load_url(self.options_url)
        self.wait_for_script("return window.OPTIONS_INITIALIZED")
        self.find_el_by_css('a[href="#tab-general-settings"]').click()
        self.find_el_by_css('#enable_dnt_checkbox').click()

        self.load_url(self.FIXTURE_URL, wait_for_body_text=True)

        # navigator.doNotTrack defaults to null in Chrome, "unspecified" in Firefox
        body_text = self.driver.find_element(By.TAG_NAME, 'body').text
        assert body_text[0:5] == 'unset', (
            "navigator.doNotTrack should be unset or \"unspecified\"")

        self.assert_navigator_gpc_unset("navigator.globalPrivacyControl should be unset or False")

    def test_navigator_toggling_dnt_and_disabled_sites(self):
        # disable on site
        self.disable_badger_on_site(self.FIXTURE_URL)

        # disable sending DNT signals
        self.load_url(self.options_url)
        self.wait_for_script("return window.OPTIONS_INITIALIZED")
        self.find_el_by_css('a[href="#tab-general-settings"]').click()
        self.find_el_by_css('#enable_dnt_checkbox').click()

        self.load_url(self.FIXTURE_URL, wait_for_body_text=True)
        assert self.js("return navigator.doNotTrack") != "1", (
            "navigator.doNotTrack should not be set")
        self.assert_navigator_gpc_unset("navigator.globalPrivacyControl should be unset or False")

        # re-enable sending DNT signals
        self.load_url(self.options_url)
        self.wait_for_script("return window.OPTIONS_INITIALIZED")
        self.find_el_by_css('a[href="#tab-general-settings"]').click()
        self.find_el_by_css('#enable_dnt_checkbox').click()

        self.load_url(self.FIXTURE_URL, wait_for_body_text=True)
        assert self.js("return navigator.doNotTrack") != "1", (
            "navigator.doNotTrack should still not be set")
        self.assert_navigator_gpc_unset("navigator.globalPrivacyControl should still be unset or False")

        # re-enable on site
        self.reenable_badger_on_site("efforg.github.io")

        self.load_url(self.FIXTURE_URL, wait_for_body_text=True)
        assert self.js("return navigator.doNotTrack") == "1", (
            "navigator.doNotTrack should now be set")
        assert self.js("return navigator.globalPrivacyControl"), (
            "navigator.globalPrivacyControl should also be set")


if __name__ == "__main__":
    unittest.main()