From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../test/unit/test_SitePermissions_temporary.js | 710 +++++++++++++++++++++ 1 file changed, 710 insertions(+) create mode 100644 browser/modules/test/unit/test_SitePermissions_temporary.js (limited to 'browser/modules/test/unit/test_SitePermissions_temporary.js') diff --git a/browser/modules/test/unit/test_SitePermissions_temporary.js b/browser/modules/test/unit/test_SitePermissions_temporary.js new file mode 100644 index 0000000000..a91b1b8bd8 --- /dev/null +++ b/browser/modules/test/unit/test_SitePermissions_temporary.js @@ -0,0 +1,710 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +const { SitePermissions } = ChromeUtils.importESModule( + "resource:///modules/SitePermissions.sys.mjs" +); + +const TemporaryPermissions = SitePermissions._temporaryPermissions; + +const PERM_A = "foo"; +const PERM_B = "bar"; +const PERM_C = "foobar"; + +const BROWSER_A = createDummyBrowser("https://example.com/foo"); +const BROWSER_B = createDummyBrowser("https://example.org/foo"); + +const EXPIRY_MS_A = 1000000; +const EXPIRY_MS_B = 1000001; + +function createDummyBrowser(spec) { + let uri = Services.io.newURI(spec); + return { + currentURI: uri, + contentPrincipal: Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ), + dispatchEvent: () => {}, + ownerGlobal: { + CustomEvent: class CustomEvent {}, + }, + }; +} + +function navigateDummyBrowser(browser, uri) { + // Callers may pass in either uri strings or nsIURI objects. + if (typeof uri == "string") { + uri = Services.io.newURI(uri); + } + browser.currentURI = uri; + browser.contentPrincipal = + Services.scriptSecurityManager.createContentPrincipal( + browser.currentURI, + {} + ); +} + +/** + * Tests that temporary permissions with different block states are stored + * (set, overwrite, delete) correctly. + */ +add_task(async function testAllowBlock() { + // Set two temporary permissions on the same browser. + SitePermissions.setForPrincipal( + null, + PERM_A, + SitePermissions.ALLOW, + SitePermissions.SCOPE_TEMPORARY, + BROWSER_A, + EXPIRY_MS_A + ); + + SitePermissions.setForPrincipal( + null, + PERM_B, + SitePermissions.BLOCK, + SitePermissions.SCOPE_TEMPORARY, + BROWSER_A, + EXPIRY_MS_A + ); + + // Test that the permissions have been set correctly. + Assert.deepEqual( + SitePermissions.getForPrincipal(null, PERM_A, BROWSER_A), + { + state: SitePermissions.ALLOW, + scope: SitePermissions.SCOPE_TEMPORARY, + }, + "SitePermissions returns expected permission state for perm A." + ); + + Assert.deepEqual( + SitePermissions.getForPrincipal(null, PERM_B, BROWSER_A), + { + state: SitePermissions.BLOCK, + scope: SitePermissions.SCOPE_TEMPORARY, + }, + "SitePermissions returns expected permission state for perm B." + ); + + Assert.deepEqual( + TemporaryPermissions.get(BROWSER_A, PERM_A), + { + id: PERM_A, + state: SitePermissions.ALLOW, + scope: SitePermissions.SCOPE_TEMPORARY, + }, + "TemporaryPermissions returns expected permission state for perm A." + ); + + Assert.deepEqual( + TemporaryPermissions.get(BROWSER_A, PERM_B), + { + id: PERM_B, + state: SitePermissions.BLOCK, + scope: SitePermissions.SCOPE_TEMPORARY, + }, + "TemporaryPermissions returns expected permission state for perm B." + ); + + // Test internal data structure of TemporaryPermissions. + let entry = TemporaryPermissions._stateByBrowser.get(BROWSER_A); + ok(entry, "Should have an entry for browser A"); + ok( + !TemporaryPermissions._stateByBrowser.has(BROWSER_B), + "Should have no entry for browser B" + ); + + let { browser, uriToPerm } = entry; + Assert.equal( + browser?.get(), + BROWSER_A, + "Entry should have a weak reference to the browser." + ); + + ok(uriToPerm, "Entry should have uriToPerm object."); + Assert.equal(Object.keys(uriToPerm).length, 2, "uriToPerm has 2 entries."); + + let permissionsA = uriToPerm[BROWSER_A.contentPrincipal.origin]; + let permissionsB = + uriToPerm[Services.eTLD.getBaseDomain(BROWSER_A.currentURI)]; + + ok(permissionsA, "Allow should be keyed under origin"); + ok(permissionsB, "Block should be keyed under baseDomain"); + + let permissionA = permissionsA[PERM_A]; + let permissionB = permissionsB[PERM_B]; + + Assert.equal( + permissionA.state, + SitePermissions.ALLOW, + "Should have correct state" + ); + let expireTimeoutA = permissionA.expireTimeout; + Assert.ok( + Number.isInteger(expireTimeoutA), + "Should have valid expire timeout" + ); + + Assert.equal( + permissionB.state, + SitePermissions.BLOCK, + "Should have correct state" + ); + let expireTimeoutB = permissionB.expireTimeout; + Assert.ok( + Number.isInteger(expireTimeoutB), + "Should have valid expire timeout" + ); + + // Overwrite permission A. + SitePermissions.setForPrincipal( + null, + PERM_A, + SitePermissions.ALLOW, + SitePermissions.SCOPE_TEMPORARY, + BROWSER_A, + EXPIRY_MS_B + ); + + Assert.ok( + permissionsA[PERM_A].expireTimeout != expireTimeoutA, + "Overwritten permission A should have new timer" + ); + + // Overwrite permission B - this time with a non-block state which means it + // should be keyed by origin now. + SitePermissions.setForPrincipal( + null, + PERM_B, + SitePermissions.ALLOW, + SitePermissions.SCOPE_TEMPORARY, + BROWSER_A, + EXPIRY_MS_A + ); + + let baseDomainEntry = + uriToPerm[Services.eTLD.getBaseDomain(BROWSER_A.currentURI)]; + Assert.ok( + !baseDomainEntry || !baseDomainEntry[PERM_B], + "Should not longer have baseDomain permission entry" + ); + + permissionsB = uriToPerm[BROWSER_A.contentPrincipal.origin]; + permissionB = permissionsB[PERM_B]; + Assert.ok( + permissionsB && permissionB, + "Overwritten permission should be keyed under origin" + ); + Assert.equal( + permissionB.state, + SitePermissions.ALLOW, + "Should have correct updated state" + ); + Assert.ok( + permissionB.expireTimeout != expireTimeoutB, + "Overwritten permission B should have new timer" + ); + + // Remove permissions + SitePermissions.removeFromPrincipal(null, PERM_A, BROWSER_A); + SitePermissions.removeFromPrincipal(null, PERM_B, BROWSER_A); + + // Test that permissions have been removed correctly + Assert.deepEqual( + SitePermissions.getForPrincipal(null, PERM_A, BROWSER_A), + { + state: SitePermissions.UNKNOWN, + scope: SitePermissions.SCOPE_PERSISTENT, + }, + "SitePermissions returns UNKNOWN state for A." + ); + + Assert.deepEqual( + SitePermissions.getForPrincipal(null, PERM_B, BROWSER_A), + { + state: SitePermissions.UNKNOWN, + scope: SitePermissions.SCOPE_PERSISTENT, + }, + "SitePermissions returns UNKNOWN state for B." + ); + + Assert.equal( + TemporaryPermissions.get(BROWSER_A, PERM_A), + null, + "TemporaryPermissions returns null for perm A." + ); + + Assert.equal( + TemporaryPermissions.get(BROWSER_A, PERM_B), + null, + "TemporaryPermissions returns null for perm B." + ); +}); + +/** + * Tests TemporaryPermissions#getAll. + */ +add_task(async function testGetAll() { + SitePermissions.setForPrincipal( + null, + PERM_A, + SitePermissions.ALLOW, + SitePermissions.SCOPE_TEMPORARY, + BROWSER_A, + EXPIRY_MS_A + ); + SitePermissions.setForPrincipal( + null, + PERM_B, + SitePermissions.BLOCK, + SitePermissions.SCOPE_TEMPORARY, + BROWSER_B, + EXPIRY_MS_A + ); + SitePermissions.setForPrincipal( + null, + PERM_C, + SitePermissions.PROMPT, + SitePermissions.SCOPE_TEMPORARY, + BROWSER_B, + EXPIRY_MS_A + ); + + Assert.deepEqual(TemporaryPermissions.getAll(BROWSER_A), [ + { + id: PERM_A, + state: SitePermissions.ALLOW, + scope: SitePermissions.SCOPE_TEMPORARY, + }, + ]); + + let permsBrowserB = TemporaryPermissions.getAll(BROWSER_B); + Assert.equal( + permsBrowserB.length, + 2, + "There should be 2 permissions set for BROWSER_B" + ); + + let permB; + let permC; + + if (permsBrowserB[0].id == PERM_B) { + permB = permsBrowserB[0]; + permC = permsBrowserB[1]; + } else { + permB = permsBrowserB[1]; + permC = permsBrowserB[0]; + } + + Assert.deepEqual(permB, { + id: PERM_B, + state: SitePermissions.BLOCK, + scope: SitePermissions.SCOPE_TEMPORARY, + }); + Assert.deepEqual(permC, { + id: PERM_C, + state: SitePermissions.PROMPT, + scope: SitePermissions.SCOPE_TEMPORARY, + }); +}); + +/** + * Tests SitePermissions#clearTemporaryBlockPermissions and + * TemporaryPermissions#clear. + */ +add_task(async function testClear() { + SitePermissions.setForPrincipal( + null, + PERM_A, + SitePermissions.ALLOW, + SitePermissions.SCOPE_TEMPORARY, + BROWSER_A, + EXPIRY_MS_A + ); + SitePermissions.setForPrincipal( + null, + PERM_B, + SitePermissions.BLOCK, + SitePermissions.SCOPE_TEMPORARY, + BROWSER_A, + EXPIRY_MS_A + ); + SitePermissions.setForPrincipal( + null, + PERM_C, + SitePermissions.BLOCK, + SitePermissions.SCOPE_TEMPORARY, + BROWSER_B, + EXPIRY_MS_A + ); + + let stateByBrowser = SitePermissions._temporaryPermissions._stateByBrowser; + + Assert.ok(stateByBrowser.has(BROWSER_A), "Browser map should have BROWSER_A"); + Assert.ok(stateByBrowser.has(BROWSER_B), "Browser map should have BROWSER_B"); + + SitePermissions.clearTemporaryBlockPermissions(BROWSER_A); + + // We only clear block permissions, so we should still see PERM_A. + Assert.deepEqual( + SitePermissions.getForPrincipal(null, PERM_A, BROWSER_A), + { + state: SitePermissions.ALLOW, + scope: SitePermissions.SCOPE_TEMPORARY, + }, + "SitePermissions returns ALLOW state for PERM_A." + ); + // We don't clear BROWSER_B so it should still be there. + Assert.ok(stateByBrowser.has(BROWSER_B), "Should still have BROWSER_B."); + + // Now clear allow permissions for A explicitly. + SitePermissions._temporaryPermissions.clear(BROWSER_A, SitePermissions.ALLOW); + + Assert.ok(!stateByBrowser.has(BROWSER_A), "Should no longer have BROWSER_A."); + let browser = stateByBrowser.get(BROWSER_B); + Assert.ok(browser, "Should still have BROWSER_B"); + + Assert.deepEqual( + SitePermissions.getForPrincipal(null, PERM_A, BROWSER_A), + { + state: SitePermissions.UNKNOWN, + scope: SitePermissions.SCOPE_PERSISTENT, + }, + "SitePermissions returns UNKNOWN state for PERM_A." + ); + Assert.deepEqual( + SitePermissions.getForPrincipal(null, PERM_B, BROWSER_A), + { + state: SitePermissions.UNKNOWN, + scope: SitePermissions.SCOPE_PERSISTENT, + }, + "SitePermissions returns UNKNOWN state for PERM_B." + ); + Assert.deepEqual( + SitePermissions.getForPrincipal(null, PERM_C, BROWSER_B), + { + state: SitePermissions.BLOCK, + scope: SitePermissions.SCOPE_TEMPORARY, + }, + "SitePermissions returns BLOCK state for PERM_C." + ); + + SitePermissions._temporaryPermissions.clear(BROWSER_B); + + Assert.ok(!stateByBrowser.has(BROWSER_B), "Should no longer have BROWSER_B."); + Assert.deepEqual( + SitePermissions.getForPrincipal(null, PERM_C, BROWSER_B), + { + state: SitePermissions.UNKNOWN, + scope: SitePermissions.SCOPE_PERSISTENT, + }, + "SitePermissions returns UNKNOWN state for PERM_C." + ); +}); + +/** + * Tests that the temporary permissions setter calls the callback on permission + * expire with the associated browser. + */ +add_task(async function testCallbackOnExpiry() { + let promiseExpireA = new Promise(resolve => { + TemporaryPermissions.set( + BROWSER_A, + PERM_A, + SitePermissions.BLOCK, + 100, + undefined, + resolve + ); + }); + let promiseExpireB = new Promise(resolve => { + TemporaryPermissions.set( + BROWSER_B, + PERM_A, + SitePermissions.BLOCK, + 100, + BROWSER_B.contentPrincipal, + resolve + ); + }); + + let [browserA, browserB] = await Promise.all([ + promiseExpireA, + promiseExpireB, + ]); + Assert.equal( + browserA, + BROWSER_A, + "Should get callback with browser on expiry for A" + ); + Assert.equal( + browserB, + BROWSER_B, + "Should get callback with browser on expiry for B" + ); +}); + +/** + * Tests that the temporary permissions setter calls the callback on permission + * expire with the associated browser if the browser associated browser has + * changed after setting the permission. + */ +add_task(async function testCallbackOnExpiryUpdatedBrowser() { + let promiseExpire = new Promise(resolve => { + TemporaryPermissions.set( + BROWSER_A, + PERM_A, + SitePermissions.BLOCK, + 200, + undefined, + resolve + ); + }); + + TemporaryPermissions.copy(BROWSER_A, BROWSER_B); + + let browser = await promiseExpire; + Assert.equal( + browser, + BROWSER_B, + "Should get callback with updated browser on expiry." + ); +}); + +/** + * Tests that the permission setter throws an exception if an invalid expiry + * time is passed. + */ +add_task(async function testInvalidExpiryTime() { + let expectedError = /expireTime must be a positive integer/; + Assert.throws(() => { + SitePermissions.setForPrincipal( + null, + PERM_A, + SitePermissions.ALLOW, + SitePermissions.SCOPE_TEMPORARY, + BROWSER_A, + null + ); + }, expectedError); + Assert.throws(() => { + SitePermissions.setForPrincipal( + null, + PERM_A, + SitePermissions.ALLOW, + SitePermissions.SCOPE_TEMPORARY, + BROWSER_A, + 0 + ); + }, expectedError); + Assert.throws(() => { + SitePermissions.setForPrincipal( + null, + PERM_A, + SitePermissions.ALLOW, + SitePermissions.SCOPE_TEMPORARY, + BROWSER_A, + -100 + ); + }, expectedError); +}); + +/** + * Tests that we block by base domain but allow by origin. + */ +add_task(async function testTemporaryPermissionScope() { + let states = { + strict: { + same: [ + "https://example.com", + "https://example.com/sub/path", + "https://example.com:443", + "https://name:password@example.com", + ], + different: [ + "https://example.com", + "https://test1.example.com", + "http://example.com", + "http://example.org", + "file:///tmp/localPageA.html", + "file:///tmp/localPageB.html", + ], + }, + nonStrict: { + same: [ + "https://example.com", + "https://example.com/sub/path", + "https://example.com:443", + "https://test1.example.com", + "http://test2.test1.example.com", + "https://name:password@example.com", + "http://example.com", + ], + different: [ + "https://example.com", + "https://example.org", + "http://example.net", + ], + }, + }; + + for (let state of [SitePermissions.BLOCK, SitePermissions.ALLOW]) { + let matchStrict = state != SitePermissions.BLOCK; + + let lists = matchStrict ? states.strict : states.nonStrict; + + Object.entries(lists).forEach(([type, list]) => { + let expectSet = type == "same"; + + for (let uri of list) { + let browser = createDummyBrowser(uri); + SitePermissions.setForPrincipal( + null, + PERM_A, + state, + SitePermissions.SCOPE_TEMPORARY, + browser, + EXPIRY_MS_A + ); + + ok(true, "origin:" + browser.contentPrincipal.origin); + + for (let otherUri of list) { + if (uri == otherUri) { + continue; + } + navigateDummyBrowser(browser, otherUri); + ok(true, "new origin:" + browser.contentPrincipal.origin); + + Assert.deepEqual( + SitePermissions.getForPrincipal(null, PERM_A, browser), + { + state: expectSet ? state : SitePermissions.UNKNOWN, + scope: expectSet + ? SitePermissions.SCOPE_TEMPORARY + : SitePermissions.SCOPE_PERSISTENT, + }, + `${ + state == SitePermissions.BLOCK ? "Block" : "Allow" + } Permission originally set for ${uri} should ${ + expectSet ? "not" : "also" + } be set for ${otherUri}.` + ); + } + + SitePermissions._temporaryPermissions.clear(browser); + } + }); + } +}); + +/** + * Tests that we can override the principal to use for keying temporary + * permissions. + */ +add_task(async function testOverrideBrowserURI() { + let testBrowser = createDummyBrowser("https://old.example.com/foo"); + let overrideURI = Services.io.newURI("https://test.example.org/test/path"); + SitePermissions.setForPrincipal( + Services.scriptSecurityManager.createContentPrincipal(overrideURI, {}), + PERM_A, + SitePermissions.ALLOW, + SitePermissions.SCOPE_TEMPORARY, + testBrowser, + EXPIRY_MS_A + ); + + Assert.deepEqual( + SitePermissions.getForPrincipal(null, PERM_A, testBrowser), + { + state: SitePermissions.UNKNOWN, + scope: SitePermissions.SCOPE_PERSISTENT, + }, + "Permission should not be set for old URI." + ); + + // "Navigate" to new URI + navigateDummyBrowser(testBrowser, overrideURI); + + Assert.deepEqual( + SitePermissions.getForPrincipal(null, PERM_A, testBrowser), + { + state: SitePermissions.ALLOW, + scope: SitePermissions.SCOPE_TEMPORARY, + }, + "Permission should be set for new URI." + ); + + SitePermissions._temporaryPermissions.clear(testBrowser); +}); + +/** + * Tests that TemporaryPermissions does not throw for incompatible URI or + * browser.currentURI. + */ +add_task(async function testPermissionUnsupportedScheme() { + let aboutURI = Services.io.newURI("about:blank"); + + // Incompatible override URI should not throw or store any permissions. + SitePermissions.setForPrincipal( + Services.scriptSecurityManager.createContentPrincipal(aboutURI, {}), + PERM_A, + SitePermissions.ALLOW, + SitePermissions.SCOPE_TEMPORARY, + BROWSER_A, + EXPIRY_MS_B + ); + Assert.ok( + SitePermissions._temporaryPermissions._stateByBrowser.has(BROWSER_A), + "Should not have stored permission for unsupported URI scheme." + ); + + let browser = createDummyBrowser("https://example.com/"); + // Set a permission so we get an entry in the browser map. + SitePermissions.setForPrincipal( + null, + PERM_B, + SitePermissions.BLOCK, + SitePermissions.SCOPE_TEMPORARY, + browser + ); + + // Change browser URI to about:blank. + navigateDummyBrowser(browser, aboutURI); + + // Setting permission for browser with unsupported URI should not throw. + SitePermissions.setForPrincipal( + null, + PERM_A, + SitePermissions.ALLOW, + SitePermissions.SCOPE_TEMPORARY, + browser + ); + Assert.ok(true, "Set should not throw for unsupported URI"); + + SitePermissions.removeFromPrincipal(null, PERM_A, browser); + Assert.ok(true, "Remove should not throw for unsupported URI"); + + Assert.deepEqual( + SitePermissions.getForPrincipal(null, PERM_A, browser), + { + state: SitePermissions.UNKNOWN, + scope: SitePermissions.SCOPE_PERSISTENT, + }, + "Should return no permission set for unsupported URI." + ); + Assert.ok(true, "Get should not throw for unsupported URI"); + + // getAll should not throw, but return empty permissions array. + let permissions = SitePermissions.getAllForBrowser(browser); + Assert.ok( + Array.isArray(permissions) && !permissions.length, + "Should return empty array for browser on about:blank" + ); + + SitePermissions._temporaryPermissions.clear(browser); +}); -- cgit v1.2.3