summaryrefslogtreecommitdiffstats
path: root/tests/selenium/dnt_test.py
blob: fb3fdf8b97aa225b41712227b70fc9b3eb947dbc (plain)
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
319
320
321
322
323
324
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import json
import unittest

import pbtest

from functools import partial

from selenium.common.exceptions import NoSuchElementException

from pbtest import retry_until


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

    CHECK_FOR_DNT_POLICY_JS = (
        "chrome.extension.getBackgroundPage()."
        "badger.checkForDNTPolicy("
        "  arguments[0],"
        "  r => window.DNT_CHECK_RESULT = r"
        ");"
    )

    # TODO switch to non-delayed version
    # https://gist.github.com/ghostwords/9fc6900566a2f93edd8e4a1e48bbaa28
    # once race condition (https://crbug.com/478183) is fixed
    NAVIGATOR_DNT_TEST_URL = (
        "https://efforg.github.io/privacybadger-test-fixtures/html/"
        "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:
            headers = json.loads(text)['headers']
        except ValueError:
            print("\nFailed to parse JSON from {}".format(repr(text)))
            return None

        return headers

    def domain_was_recorded(self, domain):
        return self.js(
            "return (Object.keys("
            "  chrome.extension.getBackgroundPage()."
            "  badger.storage.action_map.getItemClones()"
            ").indexOf(arguments[0]) != -1);",
            domain
        )

    def domain_was_detected(self, domain):
        return self.js(
            "return (Object.keys(chrome.extension.getBackgroundPage().badger.tabData).some(tab_id => {"
            "  let origins = chrome.extension.getBackgroundPage().badger.tabData[tab_id].origins;"
            "  return origins.hasOwnProperty(arguments[0]);"
            "}));",
            domain
        )

    def domain_was_blocked(self, domain):
        return self.js(
            "return (Object.keys(chrome.extension.getBackgroundPage().badger.tabData).some(tab_id => {"
            "  let origins = chrome.extension.getBackgroundPage().badger.tabData[tab_id].origins;"
            "  return ("
            "    origins.hasOwnProperty(arguments[0]) &&"
            "    chrome.extension.getBackgroundPage().constants.BLOCKED_ACTIONS.has(origins[arguments[0]])"
            "  );"
            "}));",
            domain
        )

    @pbtest.repeat_if_failed(3)
    def test_dnt_policy_check_should_happen_for_blocked_domains(self):
        PAGE_URL = (
            "https://efforg.github.io/privacybadger-test-fixtures/html/"
            "dnt.html"
        )
        DNT_DOMAIN = "www.eff.org"

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

        # visit a page that loads a resource from that DNT-compliant domain
        self.open_window()
        self.load_url(PAGE_URL)

        # switch back to Badger's options page
        self.switch_to_window_with_url(self.options_url)

        # verify that the domain is blocked
        self.assertTrue(self.domain_was_detected(DNT_DOMAIN),
            msg="Domain should have been detected.")
        self.assertTrue(self.domain_was_blocked(DNT_DOMAIN),
            msg="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)

            # switch back to Badger's options page
            self.switch_to_window_with_url(self.options_url)

            return (
                self.domain_was_detected(DNT_DOMAIN) and
                self.domain_was_blocked(DNT_DOMAIN)
            )

        # 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 ...")

        self.assertFalse(was_blocked,
            msg="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 = "https://{}/".format(TEST_DOMAIN)

        # verify that the domain itself doesn't set cookies
        self.load_url(TEST_URL)
        self.assertEqual(len(self.driver.get_cookies()), 0,
            "No cookies initially")

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

        # verify we got a cookie
        self.load_url(TEST_URL)
        self.assertEqual(len(self.driver.get_cookies()), 1,
            "We still have just one cookie")

        # clear cookies and verify
        self.driver.delete_all_cookies()
        self.load_url(TEST_URL)
        self.assertEqual(len(self.driver.get_cookies()), 0,
            "No cookies again")

        self.load_url(self.options_url)
        # perform a DNT policy check
        self.js(DntTest.CHECK_FOR_DNT_POLICY_JS, TEST_DOMAIN)
        # wait until checkForDNTPolicy completed
        self.wait_for_script("return window.DNT_CHECK_RESULT === false")

        # check that we didn't get cookied by the DNT URL
        self.load_url(TEST_URL)
        self.assertEqual(len(self.driver.get_cookies()), 0,
            "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 = "https://{}/".format(TEST_DOMAIN)

        # directly visit a DNT policy URL known to set cookies
        self.load_url(TEST_URL + ".well-known/dnt-policy.txt")
        self.assertEqual(len(self.driver.get_cookies()), 1,
            "DNT policy URL 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
        # MEGAHACK: make sha1 of "cookies=0" a valid DNT hash
        self.load_url(self.options_url)
        self.js(
            "chrome.extension.getBackgroundPage()."
            "badger.storage.updateDntHashes({"
            "  'cookies=0 test policy': 'f63ee614ebd77f8634b92633c6bb809a64b9a3d7'"
            "});"
        )

        # perform a DNT policy check
        self.js(DntTest.CHECK_FOR_DNT_POLICY_JS, TEST_DOMAIN)
        # wait until checkForDNTPolicy completed
        self.wait_for_script("return typeof window.DNT_CHECK_RESULT != 'undefined';")
        # get the result
        result = self.js("return window.DNT_CHECK_RESULT;")
        self.assertTrue(result, "No cookies were sent")

    def test_should_not_record_nontracking_domains(self):
        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.load_url(self.options_url)
        self.js("chrome.extension.getBackgroundPage().badger.storage.clearTrackerData();")

        # enable local learning
        self.wait_for_script("return window.OPTIONS_INITIALIZED")
        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(FIXTURE_URL)

        # verify both domains are present on the page
        try:
            selector = "iframe[src*='%s']" % 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 = "img[src*='%s']" % 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")

        self.load_url(self.options_url)

        # verify that the cookie-tracking domain was recorded
        self.assertTrue(
            self.domain_was_recorded(TRACKING_DOMAIN),
            "Tracking domain should have gotten recorded"
        )

        # verify that the non-tracking domain was not recorded
        self.assertFalse(
            self.domain_was_recorded(NON_TRACKING_DOMAIN),
            "Non-tracking domain should not have gotten recorded"
        )

    def test_first_party_dnt_header(self):
        TEST_URL = "https://httpbin.org/get"
        headers = retry_until(partial(self.get_first_party_headers, TEST_URL),
                              times=8)
        self.assertTrue(headers is not None, "It seems we failed to get headers")
        self.assertIn('Dnt', headers, "DNT header should have been present")
        self.assertIn('Sec-Gpc', headers, "GPC header should have been present")
        self.assertEqual(headers['Dnt'], "1",
            'DNT header should have been set to "1"')
        self.assertEqual(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://httpbin.org/get"
        self.disable_badger_on_site(TEST_URL)
        headers = retry_until(partial(self.get_first_party_headers, TEST_URL),
                              times=8)
        self.assertTrue(headers is not None, "It seems we failed to get headers")
        self.assertNotIn('Dnt', headers, "DNT header should have been missing")
        self.assertNotIn('Sec-Gpc', headers, "GPC header should have been missing")

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

        self.load_url(self.options_url)
        self.wait_for_script("return window.OPTIONS_INITIALIZED")
        self.find_el_by_css('#enable_dnt_checkbox').click()

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

    def test_navigator_object(self):
        self.load_url(DntTest.NAVIGATOR_DNT_TEST_URL, wait_for_body_text=True)

        self.assertEqual(
            self.driver.find_element_by_tag_name('body').text,
            'no tracking (navigator.doNotTrack="1")',
            "navigator.DoNotTrack should have been set to \"1\""
        )
        self.assertEqual(
            self.js("return navigator.globalPrivacyControl"),
            "1",
            "navigator.globalPrivacyControl should have been set to \"1\""
        )

    def test_navigator_unmodified_when_disabled_on_site(self):
        self.disable_badger_on_site(DntTest.NAVIGATOR_DNT_TEST_URL)

        self.load_url(DntTest.NAVIGATOR_DNT_TEST_URL, wait_for_body_text=True)

        # navigator.doNotTrack defaults to null in Chrome, "unspecified" in Firefox
        self.assertEqual(
            self.driver.find_element_by_tag_name('body').text[0:5],
            'unset',
            "navigator.DoNotTrack should have been left unset"
        )
        self.assertEqual(
            self.js("return navigator.globalPrivacyControl"),
            None,
            "navigator.globalPrivacyControl should have been left unset"
        )

    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('#enable_dnt_checkbox').click()

        self.load_url(DntTest.NAVIGATOR_DNT_TEST_URL, wait_for_body_text=True)

        # navigator.doNotTrack defaults to null in Chrome, "unspecified" in Firefox
        self.assertEqual(
            self.driver.find_element_by_tag_name('body').text[0:5],
            'unset',
            "navigator.DoNotTrack should have been left unset"
        )
        self.assertEqual(
            self.js("return navigator.globalPrivacyControl"),
            None,
            "navigator.globalPrivacyControl should have been left unset"
        )


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