diff options
Diffstat (limited to 'toolkit/components/cleardata/tests/unit')
16 files changed, 4347 insertions, 0 deletions
diff --git a/toolkit/components/cleardata/tests/unit/head.js b/toolkit/components/cleardata/tests/unit/head.js new file mode 100644 index 0000000000..5e73b8a789 --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/head.js @@ -0,0 +1,27 @@ +"use strict"; + +const { SiteDataTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/SiteDataTestUtils.sys.mjs" +); +const { PermissionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PermissionTestUtils.sys.mjs" +); + +function run_test() { + do_get_profile(); + run_next_test(); +} + +function getOAWithPartitionKey( + { scheme = "https", topLevelBaseDomain, port = null } = {}, + originAttributes = {} +) { + if (!topLevelBaseDomain || !scheme) { + return originAttributes; + } + + return { + ...originAttributes, + partitionKey: `(${scheme},${topLevelBaseDomain}${port ? `,${port}` : ""})`, + }; +} diff --git a/toolkit/components/cleardata/tests/unit/test_basic.js b/toolkit/components/cleardata/tests/unit/test_basic.js new file mode 100644 index 0000000000..3634483ee4 --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_basic.js @@ -0,0 +1,19 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Basic test for nsIClearDataService module. + */ + +"use strict"; + +add_task(async function test_basic() { + Assert.ok(!!Services.clearData); + + await new Promise(aResolve => { + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => { + Assert.equal(value, 0); + aResolve(); + }); + }); +}); diff --git a/toolkit/components/cleardata/tests/unit/test_bounce_tracking_protection.js b/toolkit/components/cleardata/tests/unit/test_bounce_tracking_protection.js new file mode 100644 index 0000000000..ac0eb40fd9 --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_bounce_tracking_protection.js @@ -0,0 +1,625 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "bounceTrackingProtection", + "@mozilla.org/bounce-tracking-protection;1", + "nsIBounceTrackingProtection" +); + +const { CLEAR_BOUNCE_TRACKING_PROTECTION_STATE } = Ci.nsIClearDataService; + +const OA_DEFAULT = {}; +const OA_PRIVATE_BROWSING = { privateBrowsingId: 1 }; +const OA_CONTAINER = { userContextId: 1 }; + +function addTestData() { + info("Adding test data."); + // Add test data for OriginAttributes {} => normal browsing with no container. + bounceTrackingProtection.testAddBounceTrackerCandidate( + OA_DEFAULT, + "common-bounce-tracker.com", + 100 + ); + bounceTrackingProtection.testAddBounceTrackerCandidate( + OA_DEFAULT, + "bounce-tracker-normal-browsing.com", + 200 + ); + bounceTrackingProtection.testAddBounceTrackerCandidate( + OA_DEFAULT, + "bounce-tracker-normal-browsing2.com", + 300 + ); + bounceTrackingProtection.testAddUserActivation( + OA_DEFAULT, + "common-user-activation.com", + 400 + ); + bounceTrackingProtection.testAddUserActivation( + OA_DEFAULT, + "user-activation-normal-browsing.com", + 500 + ); + bounceTrackingProtection.testAddUserActivation( + OA_DEFAULT, + "user-activation-normal-browsing2.com", + 600 + ); + + // Add test data for private browsing. + bounceTrackingProtection.testAddBounceTrackerCandidate( + OA_PRIVATE_BROWSING, + "common-bounce-tracker.com", + 700 + ); + bounceTrackingProtection.testAddBounceTrackerCandidate( + OA_PRIVATE_BROWSING, + "bounce-tracker-private-browsing.com", + 800 + ); + bounceTrackingProtection.testAddBounceTrackerCandidate( + OA_PRIVATE_BROWSING, + "bounce-tracker-private-browsing2.com", + 900 + ); + bounceTrackingProtection.testAddUserActivation( + OA_PRIVATE_BROWSING, + "common-user-activation.com", + 1000 + ); + bounceTrackingProtection.testAddUserActivation( + OA_PRIVATE_BROWSING, + "user-activation-private-browsing.com", + 1100 + ); + bounceTrackingProtection.testAddUserActivation( + OA_PRIVATE_BROWSING, + "user-activation-private-browsing2.com", + 1200 + ); + + // Add test data for a container. + bounceTrackingProtection.testAddBounceTrackerCandidate( + OA_CONTAINER, + "common-bounce-tracker.com", + 1300 + ); + bounceTrackingProtection.testAddBounceTrackerCandidate( + OA_CONTAINER, + "bounce-tracker-container.com", + 1400 + ); + bounceTrackingProtection.testAddBounceTrackerCandidate( + OA_CONTAINER, + "bounce-tracker-container2.com", + 1500 + ); + bounceTrackingProtection.testAddUserActivation( + OA_CONTAINER, + "common-user-activation.com", + 1600 + ); + bounceTrackingProtection.testAddUserActivation( + OA_CONTAINER, + "user-activation-container.com", + 1700 + ); + bounceTrackingProtection.testAddUserActivation( + OA_CONTAINER, + "user-activation-container2.com", + 1800 + ); +} + +async function runDeleteBySiteHostTest(clearDataServiceFn) { + addTestData(); + + let baseDomain = "common-bounce-tracker.com"; + info("Deleting by base domain " + baseDomain); + await new Promise(function (resolve) { + clearDataServiceFn( + baseDomain, + true, + CLEAR_BOUNCE_TRACKING_PROTECTION_STATE, + failedFlags => { + Assert.equal(failedFlags, 0, "Clearing should have succeeded"); + resolve(); + } + ); + }); + + Assert.deepEqual( + bounceTrackingProtection + .testGetBounceTrackerCandidateHosts(OA_DEFAULT) + .sort(), + [ + "bounce-tracker-normal-browsing.com", + "bounce-tracker-normal-browsing2.com", + ], + "Should have deleted only 'common-bounce-tracker.com' for default OA." + ); + Assert.deepEqual( + bounceTrackingProtection.testGetUserActivationHosts(OA_DEFAULT).sort(), + [ + "common-user-activation.com", + "user-activation-normal-browsing.com", + "user-activation-normal-browsing2.com", + ], + "Should not have deleted any user activations for default OA." + ); + + Assert.deepEqual( + bounceTrackingProtection + .testGetBounceTrackerCandidateHosts(OA_PRIVATE_BROWSING) + .sort(), + [ + "bounce-tracker-private-browsing.com", + "bounce-tracker-private-browsing2.com", + ], + "Should have deleted only 'common-bounce-tracker.com' for private browsing." + ); + Assert.deepEqual( + bounceTrackingProtection + .testGetUserActivationHosts(OA_PRIVATE_BROWSING) + .sort(), + [ + "common-user-activation.com", + "user-activation-private-browsing.com", + "user-activation-private-browsing2.com", + ], + "Should not have deleted any user activations for private browsing." + ); + + Assert.deepEqual( + bounceTrackingProtection + .testGetBounceTrackerCandidateHosts(OA_CONTAINER) + .sort(), + ["bounce-tracker-container.com", "bounce-tracker-container2.com"], + "Should have deleted only 'common-bounce-tracker.com' for container." + ); + + Assert.deepEqual( + bounceTrackingProtection.testGetUserActivationHosts(OA_CONTAINER).sort(), + [ + "common-user-activation.com", + "user-activation-container.com", + "user-activation-container2.com", + ], + "Should not have deleted any user activations for container." + ); + + // Cleanup. + bounceTrackingProtection.clearAll(); +} + +do_get_profile(); + +add_task(async function test_deleteAll() { + addTestData(); + + info("Deleting all data."); + await new Promise(function (resolve) { + Services.clearData.deleteData( + CLEAR_BOUNCE_TRACKING_PROTECTION_STATE, + failedFlags => { + Assert.equal(failedFlags, 0, "Clearing should have succeeded"); + resolve(); + } + ); + }); + + Assert.equal( + bounceTrackingProtection.testGetBounceTrackerCandidateHosts(OA_DEFAULT) + .length, + 0, + "All bounce tracker candidates for default OA should be deleted." + ); + Assert.equal( + bounceTrackingProtection.testGetUserActivationHosts(OA_DEFAULT).length, + 0, + "All user activations for default OA should be deleted." + ); + + Assert.equal( + bounceTrackingProtection.testGetBounceTrackerCandidateHosts( + OA_PRIVATE_BROWSING + ).length, + 0, + "All bounce tracker candidates for private browsing should be deleted." + ); + Assert.equal( + bounceTrackingProtection.testGetUserActivationHosts(OA_PRIVATE_BROWSING) + .length, + 0, + "All user activations for private browsing should be deleted." + ); + + Assert.equal( + bounceTrackingProtection.testGetBounceTrackerCandidateHosts(OA_CONTAINER) + .length, + 0, + "All bounce tracker candidates for container 1 should be deleted." + ); + Assert.equal( + bounceTrackingProtection.testGetUserActivationHosts(OA_CONTAINER).length, + 0, + "All user activations for container 1 should be deleted." + ); +}); + +add_task(async function test_deleteByPrincipal() { + addTestData(); + + let principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://common-bounce-tracker.com"), + {} + ); + console.debug("principal", principal.origin); + + info("Deleting by principal " + principal.origin); + await new Promise(function (resolve) { + Services.clearData.deleteDataFromPrincipal( + principal, + false, + CLEAR_BOUNCE_TRACKING_PROTECTION_STATE, + failedFlags => { + Assert.equal(failedFlags, 0, "Clearing should have succeeded"); + resolve(); + } + ); + }); + + Assert.deepEqual( + bounceTrackingProtection + .testGetBounceTrackerCandidateHosts(OA_DEFAULT) + .sort(), + [ + "bounce-tracker-normal-browsing.com", + "bounce-tracker-normal-browsing2.com", + ], + "Should have deleted only 'common-bounce-tracker.com' for default OA." + ); + Assert.deepEqual( + bounceTrackingProtection.testGetUserActivationHosts(OA_DEFAULT).sort(), + [ + "common-user-activation.com", + "user-activation-normal-browsing.com", + "user-activation-normal-browsing2.com", + ], + "Should not have deleted any user activations for default OA." + ); + + Assert.deepEqual( + bounceTrackingProtection + .testGetBounceTrackerCandidateHosts(OA_PRIVATE_BROWSING) + .sort(), + [ + "bounce-tracker-private-browsing.com", + "bounce-tracker-private-browsing2.com", + "common-bounce-tracker.com", + ], + "Should not have deleted 'common-bounce-tracker.com' for private browsing." + ); + Assert.deepEqual( + bounceTrackingProtection + .testGetUserActivationHosts(OA_PRIVATE_BROWSING) + .sort(), + [ + "common-user-activation.com", + "user-activation-private-browsing.com", + "user-activation-private-browsing2.com", + ], + "Should not have deleted any user activations for private browsing." + ); + + Assert.deepEqual( + bounceTrackingProtection + .testGetBounceTrackerCandidateHosts(OA_CONTAINER) + .sort(), + [ + "bounce-tracker-container.com", + "bounce-tracker-container2.com", + "common-bounce-tracker.com", + ], + "Should not have deleted 'common-bounce-tracker.com' for container." + ); + Assert.deepEqual( + bounceTrackingProtection.testGetUserActivationHosts(OA_CONTAINER).sort(), + [ + "common-user-activation.com", + "user-activation-container.com", + "user-activation-container2.com", + ], + "Should not have deleted any user activations for container." + ); + + let principal2 = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://common-user-activation.com"), + OA_CONTAINER + ); + + info("Deleting by principal " + principal2.origin); + await new Promise(function (resolve) { + Services.clearData.deleteDataFromPrincipal( + principal2, + false, + CLEAR_BOUNCE_TRACKING_PROTECTION_STATE, + failedFlags => { + Assert.equal(failedFlags, 0, "Clearing should have succeeded"); + resolve(); + } + ); + }); + + Assert.deepEqual( + bounceTrackingProtection + .testGetBounceTrackerCandidateHosts(OA_DEFAULT) + .sort(), + [ + "bounce-tracker-normal-browsing.com", + "bounce-tracker-normal-browsing2.com", + ], + "Should not have deleted any bounce tracker candidates for default OA." + ); + Assert.deepEqual( + bounceTrackingProtection.testGetUserActivationHosts(OA_DEFAULT).sort(), + [ + "common-user-activation.com", + "user-activation-normal-browsing.com", + "user-activation-normal-browsing2.com", + ], + "Should not have deleted 'common-user-activation.com' for default OA." + ); + + Assert.deepEqual( + bounceTrackingProtection + .testGetBounceTrackerCandidateHosts(OA_PRIVATE_BROWSING) + .sort(), + [ + "bounce-tracker-private-browsing.com", + "bounce-tracker-private-browsing2.com", + "common-bounce-tracker.com", + ], + "Should not have deleted any bounce tracker candidates for private browsing." + ); + Assert.deepEqual( + bounceTrackingProtection + .testGetUserActivationHosts(OA_PRIVATE_BROWSING) + .sort(), + [ + "common-user-activation.com", + "user-activation-private-browsing.com", + "user-activation-private-browsing2.com", + ], + "Should not have deleted 'common-user-activation.com' for private browsing." + ); + + Assert.deepEqual( + bounceTrackingProtection + .testGetBounceTrackerCandidateHosts(OA_CONTAINER) + .sort(), + [ + "bounce-tracker-container.com", + "bounce-tracker-container2.com", + "common-bounce-tracker.com", + ], + "Should not have deleted any bounce tracker candidates for container." + ); + Assert.deepEqual( + bounceTrackingProtection.testGetUserActivationHosts(OA_CONTAINER).sort(), + ["user-activation-container.com", "user-activation-container2.com"], + "Should have deleted 'common-user-activation.com' for private browsing.." + ); + + // Cleanup. + bounceTrackingProtection.clearAll(); +}); + +add_task(async function test_deleteByBaseDomain() { + await runDeleteBySiteHostTest(Services.clearData.deleteDataFromBaseDomain); +}); + +add_task(async function test_deleteByRange() { + addTestData(); + + let startTime = 200; + let endTime = 1300; + + info(`Deleting by range ${startTime} - ${endTime}`); + await new Promise(function (resolve) { + Services.clearData.deleteDataInTimeRange( + startTime, + endTime, + true, + CLEAR_BOUNCE_TRACKING_PROTECTION_STATE, + failedFlags => { + Assert.equal(failedFlags, 0, "Clearing should have succeeded"); + resolve(); + } + ); + }); + + Assert.deepEqual( + bounceTrackingProtection + .testGetBounceTrackerCandidateHosts(OA_DEFAULT) + .sort(), + ["common-bounce-tracker.com"], + "Should have only kept 'common-bounce-tracker.com' for default OA." + ); + Assert.deepEqual( + bounceTrackingProtection.testGetUserActivationHosts(OA_DEFAULT).sort(), + [], + "Should not have kept any user activations for default OA." + ); + + Assert.deepEqual( + bounceTrackingProtection + .testGetBounceTrackerCandidateHosts(OA_PRIVATE_BROWSING) + .sort(), + [], + "Should not have kept any bounces for private browsing." + ); + Assert.deepEqual( + bounceTrackingProtection + .testGetUserActivationHosts(OA_PRIVATE_BROWSING) + .sort(), + [], + "Should not have kept any user activations for private browsing." + ); + + Assert.deepEqual( + bounceTrackingProtection + .testGetBounceTrackerCandidateHosts(OA_CONTAINER) + .sort(), + ["bounce-tracker-container.com", "bounce-tracker-container2.com"], + "Should have only kept some bouncer trackers for container." + ); + Assert.deepEqual( + bounceTrackingProtection.testGetUserActivationHosts(OA_CONTAINER).sort(), + [ + "common-user-activation.com", + "user-activation-container.com", + "user-activation-container2.com", + ], + "Should have kept all user activations for container." + ); + + // Cleanup. + bounceTrackingProtection.clearAll(); +}); + +add_task(async function test_deleteByHost() { + await runDeleteBySiteHostTest(Services.clearData.deleteDataFromHost); +}); + +add_task(async function test_deleteByOriginAttributes() { + addTestData(); + + info("Deleting by origin attributes. " + JSON.stringify(OA_CONTAINER)); + await new Promise(function (resolve) { + Services.clearData.deleteDataFromOriginAttributesPattern( + OA_CONTAINER, + failedFlags => { + Assert.equal(failedFlags, 0, "Clearing should have succeeded"); + resolve(); + } + ); + }); + + Assert.deepEqual( + bounceTrackingProtection + .testGetBounceTrackerCandidateHosts(OA_DEFAULT) + .sort(), + [ + "bounce-tracker-normal-browsing.com", + "bounce-tracker-normal-browsing2.com", + "common-bounce-tracker.com", + ], + "Should not have deleted any bounce tracker candidates for default OA." + ); + Assert.deepEqual( + bounceTrackingProtection.testGetUserActivationHosts(OA_DEFAULT).sort(), + [ + "common-user-activation.com", + "user-activation-normal-browsing.com", + "user-activation-normal-browsing2.com", + ], + "Should not have deleted any user activations for default OA." + ); + + Assert.deepEqual( + bounceTrackingProtection + .testGetBounceTrackerCandidateHosts(OA_PRIVATE_BROWSING) + .sort(), + [ + "bounce-tracker-private-browsing.com", + "bounce-tracker-private-browsing2.com", + "common-bounce-tracker.com", + ], + "Should not have deleted any bounce tracker candidates for private browsing." + ); + Assert.deepEqual( + bounceTrackingProtection + .testGetUserActivationHosts(OA_PRIVATE_BROWSING) + .sort(), + [ + "common-user-activation.com", + "user-activation-private-browsing.com", + "user-activation-private-browsing2.com", + ], + "Should not have deleted any user activations for private browsing." + ); + + Assert.deepEqual( + bounceTrackingProtection + .testGetBounceTrackerCandidateHosts(OA_CONTAINER) + .sort(), + [], + "Should have deleted all bounce tracker candidates for container." + ); + Assert.deepEqual( + bounceTrackingProtection.testGetUserActivationHosts(OA_CONTAINER).sort(), + [], + "Should have deleted all user activations for container." + ); + + info("Deleting by origin attributes {} (all)."); + await new Promise(function (resolve) { + Services.clearData.deleteDataFromOriginAttributesPattern( + OA_DEFAULT, + failedFlags => { + Assert.equal(failedFlags, 0, "Clearing should have succeeded"); + resolve(); + } + ); + }); + + Assert.equal( + bounceTrackingProtection.testGetBounceTrackerCandidateHosts(OA_DEFAULT) + .length, + 0, + "Should have deleted all bounce tracker candidates for default OA." + ); + Assert.equal( + bounceTrackingProtection.testGetUserActivationHosts(OA_DEFAULT).length, + 0, + "Should have deleted all user activations for default OA." + ); + + Assert.equal( + bounceTrackingProtection.testGetBounceTrackerCandidateHosts( + OA_PRIVATE_BROWSING + ).length, + 0, + "Should have deleted all bounce tracker candidates for private browsing." + ); + Assert.equal( + bounceTrackingProtection.testGetUserActivationHosts(OA_PRIVATE_BROWSING) + .length, + 0, + "Should have deleted all user activations for private browsing." + ); + + Assert.equal( + bounceTrackingProtection.testGetBounceTrackerCandidateHosts(OA_CONTAINER) + .length, + 0, + "Should have deleted all bounce tracker candidates for container." + ); + Assert.equal( + bounceTrackingProtection.testGetUserActivationHosts(OA_CONTAINER).length, + 0, + "Should have deleted all user activations for container." + ); + + // Cleanup. + bounceTrackingProtection.clearAll(); +}); diff --git a/toolkit/components/cleardata/tests/unit/test_certs.js b/toolkit/components/cleardata/tests/unit/test_certs.js new file mode 100644 index 0000000000..3ff538d5a8 --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_certs.js @@ -0,0 +1,233 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const overrideService = Cc["@mozilla.org/security/certoverride;1"].getService( + Ci.nsICertOverrideService +); +const certDB = Cc["@mozilla.org/security/x509certdb;1"].getService( + Ci.nsIX509CertDB +); + +const CERT_TEST = + "MIHhMIGcAgEAMA0GCSqGSIb3DQEBBQUAMAwxCjAIBgNVBAMTAUEwHhcNMTEwMzIzMjMyNTE3WhcNMTEwNDIyMjMyNTE3WjAMMQowCAYDVQQDEwFBMEwwDQYJKoZIhvcNAQEBBQADOwAwOAIxANFm7ZCfYNJViaDWTFuMClX3+9u18VFGiyLfM6xJrxir4QVtQC7VUC/WUGoBUs9COQIDAQABMA0GCSqGSIb3DQEBBQUAAzEAx2+gIwmuYjJO5SyabqIm4lB1MandHH1HQc0y0tUFshBOMESTzQRPSVwPn77a6R9t"; + +add_task(async function () { + Assert.ok(Services.clearData); + + const TEST_URI = Services.io.newURI("http://test.com/"); + const ANOTHER_TEST_URI = Services.io.newURI("https://example.com/"); + const YET_ANOTHER_TEST_URI = Services.io.newURI("https://example.test/"); + let cert = certDB.constructX509FromBase64(CERT_TEST); + let flags = Ci.nsIClearDataService.CLEAR_CERT_EXCEPTIONS; + + ok(cert, "Cert was created"); + + Assert.ok( + !overrideService.hasMatchingOverride( + TEST_URI.asciiHost, + TEST_URI.port, + {}, + cert, + {} + ), + `Should not have override for ${TEST_URI.asciiHost}:${TEST_URI.port} yet` + ); + + overrideService.rememberValidityOverride( + TEST_URI.asciiHost, + TEST_URI.port, + {}, + cert, + flags, + false + ); + + Assert.ok( + overrideService.hasMatchingOverride( + TEST_URI.asciiHost, + TEST_URI.port, + {}, + cert, + {} + ), + `Should have override for ${TEST_URI.asciiHost}:${TEST_URI.port} now` + ); + + await new Promise(aResolve => { + Services.clearData.deleteDataFromHost( + TEST_URI.asciiHostPort, + true /* user request */, + flags, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.ok( + !overrideService.hasMatchingOverride( + TEST_URI.asciiHost, + TEST_URI.port, + {}, + cert, + {} + ), + `Should not have override for ${TEST_URI.asciiHost}:${TEST_URI.port} now` + ); + + for (let uri of [TEST_URI, ANOTHER_TEST_URI, YET_ANOTHER_TEST_URI]) { + overrideService.rememberValidityOverride( + uri.asciiHost, + uri.port, + { privateBrowsingId: 1 }, + cert, + flags, + false + ); + Assert.ok( + overrideService.hasMatchingOverride( + uri.asciiHost, + uri.port, + { privateBrowsingId: 1 }, + cert, + {} + ), + `Should have added override for ${uri.asciiHost}:${uri.port} with private browsing ID` + ); + Assert.ok( + !overrideService.hasMatchingOverride( + uri.asciiHost, + uri.port, + { privateBrowsingId: 2 }, + cert, + {} + ), + `Should not have added override for ${uri.asciiHost}:${uri.port} with private browsing ID 2` + ); + Assert.ok( + !overrideService.hasMatchingOverride( + uri.asciiHost, + uri.port, + {}, + cert, + {} + ), + `Should not have added override for ${uri.asciiHost}:${uri.port}` + ); + overrideService.rememberValidityOverride( + uri.asciiHost, + uri.port, + {}, + cert, + flags, + false + ); + Assert.ok( + overrideService.hasMatchingOverride( + uri.asciiHost, + uri.port, + {}, + cert, + {} + ), + `Should have added override for ${uri.asciiHost}:${uri.port}` + ); + } + + await new Promise(aResolve => { + Services.clearData.deleteData(flags, value => { + Assert.equal(value, 0); + aResolve(); + }); + }); + + for (let uri of [TEST_URI, ANOTHER_TEST_URI, YET_ANOTHER_TEST_URI]) { + Assert.ok( + !overrideService.hasMatchingOverride( + uri.asciiHost, + uri.port, + {}, + cert, + {} + ), + `Should have removed override for ${uri.asciiHost}:${uri.port}` + ); + Assert.ok( + !overrideService.hasMatchingOverride( + uri.asciiHost, + uri.port, + { privateBrowsingId: 1 }, + cert, + {} + ), + `Should have removed override for ${uri.asciiHost}:${uri.port} with private browsing attribute` + ); + } +}); + +add_task(async function test_deleteByBaseDomain() { + let toClear = [ + Services.io.newURI("https://example.com"), + Services.io.newURI("http://example.com:8080"), + Services.io.newURI("http://test1.example.com"), + Services.io.newURI("http://foo.bar.example.com"), + ]; + + let toKeep = [ + Services.io.newURI("https://example.org"), + Services.io.newURI("http://test1.example.org"), + Services.io.newURI("http://foo.bar.example.org"), + Services.io.newURI("http://example.test"), + ]; + + let all = toClear.concat(toKeep); + + let cert = certDB.constructX509FromBase64(CERT_TEST); + ok(cert, "Cert was created"); + + all.forEach(({ asciiHost, port }) => { + Assert.ok( + !overrideService.hasMatchingOverride(asciiHost, port, {}, cert, {}), + `Should not have override for ${asciiHost}:${port} yet` + ); + + overrideService.rememberValidityOverride(asciiHost, port, {}, cert, false); + + Assert.ok( + overrideService.hasMatchingOverride(asciiHost, port, {}, cert, {}), + `Should have override for ${asciiHost}:${port} now` + ); + }); + + await new Promise(aResolve => { + Services.clearData.deleteDataFromBaseDomain( + "example.com", + true /* user request */, + Ci.nsIClearDataService.CLEAR_CERT_EXCEPTIONS, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + toClear.forEach(({ asciiHost, port }) => + Assert.ok( + !overrideService.hasMatchingOverride(asciiHost, port, {}, cert, {}), + `Should have cleared override for ${asciiHost}:${port}` + ) + ); + + toKeep.forEach(({ asciiHost, port }) => + Assert.ok( + overrideService.hasMatchingOverride(asciiHost, port, {}, cert, {}), + `Should have kept override for ${asciiHost}:${port}` + ) + ); + + // Cleanup + overrideService.clearAllOverrides(); +}); diff --git a/toolkit/components/cleardata/tests/unit/test_cookie_banner_handling.js b/toolkit/components/cleardata/tests/unit/test_cookie_banner_handling.js new file mode 100644 index 0000000000..036f97e7ca --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_cookie_banner_handling.js @@ -0,0 +1,328 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +do_get_profile(); + +add_setup(_ => { + // Init cookieBannerService and pretend we opened a profile. + let cbs = Cc["@mozilla.org/cookie-banner-service;1"].getService( + Ci.nsIObserver + ); + cbs.observe(null, "profile-after-change", null); +}); + +add_task(async function test_delete_exception() { + info("Enabling cookie banner service with MODE_REJECT"); + Services.prefs.setIntPref( + "cookiebanners.service.mode", + Ci.nsICookieBannerService.MODE_REJECT + ); + + // Test nsIClearDataService.deleteDataFromHost + info("Adding an exception for example.com"); + let uri = Services.io.newURI("https://example.com"); + + Services.cookieBanners.setDomainPref( + uri, + Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT, + false + ); + + info("Verify that the exception is properly added"); + Assert.equal( + Services.cookieBanners.getDomainPref(uri, false), + Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT, + "The exception is properly set." + ); + + info("Trigger the deleteDataFromHost"); + await new Promise(resolve => { + Services.clearData.deleteDataFromHost( + "example.com", + true /* user request */, + Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXCEPTION, + _ => { + resolve(); + } + ); + }); + + info("Verify that the exception is deleted"); + Assert.equal( + Services.cookieBanners.getDomainPref(uri, false), + Ci.nsICookieBannerService.MODE_UNSET, + "The exception is properly cleared." + ); + + // Test nsIClearDataService.deleteDataFromBaseDomain + info("Adding an exception for example.com"); + Services.cookieBanners.setDomainPref( + uri, + Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT, + false + ); + + info("Verify that the exception is properly added"); + Assert.equal( + Services.cookieBanners.getDomainPref(uri, false), + Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT, + "The exception is properly set." + ); + + info("Trigger the deleteDataFromBaseDomain"); + await new Promise(resolve => { + Services.clearData.deleteDataFromBaseDomain( + "example.com", + true /* user request */, + Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXCEPTION, + _ => { + resolve(); + } + ); + }); + + info("Verify that the exception is deleted"); + Assert.equal( + Services.cookieBanners.getDomainPref(uri, false), + Ci.nsICookieBannerService.MODE_UNSET, + "The exception is properly cleared." + ); + + // Test nsIClearDataService.deleteDataFromPrincipal + info("Adding an exception for example.com"); + Services.cookieBanners.setDomainPref( + uri, + Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT, + false + ); + + info("Verify that the exception is properly added"); + Assert.equal( + Services.cookieBanners.getDomainPref(uri, false), + Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT, + "The exception is properly set." + ); + + info("Trigger the deleteDataFromPrincipal"); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + await new Promise(resolve => { + Services.clearData.deleteDataFromPrincipal( + principal, + true /* user request */, + Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXCEPTION, + _ => { + resolve(); + } + ); + }); + + info("Verify that the exception is deleted"); + Assert.equal( + Services.cookieBanners.getDomainPref(uri, false), + Ci.nsICookieBannerService.MODE_UNSET, + "The exception is properly cleared." + ); + + // Test nsIClearDataService.deleteData + info("Adding an exception for example.com"); + Services.cookieBanners.setDomainPref( + uri, + Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT, + false + ); + + info("Verify that the exception is properly added"); + Assert.equal( + Services.cookieBanners.getDomainPref(uri, false), + Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT, + "The exception is properly set." + ); + + info("Trigger the deleteData"); + await new Promise(resolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXCEPTION, + _ => { + resolve(); + } + ); + }); + + info("Verify that the exception is deleted"); + Assert.equal( + Services.cookieBanners.getDomainPref(uri, false), + Ci.nsICookieBannerService.MODE_UNSET, + "The exception is properly cleared." + ); + + Services.prefs.clearUserPref("cookiebanners.service.mode"); +}); + +add_task(async function test_delete_executed_record() { + info("Enabling cookie banner service with MODE_REJECT"); + Services.prefs.setIntPref( + "cookiebanners.service.mode", + Ci.nsICookieBannerService.MODE_REJECT + ); + Services.prefs.setIntPref( + "cookiebanners.bannerClicking.maxTriesPerSiteAndSession", + 1 + ); + + // Test nsIClearDataService.deleteDataFromHost + info("Adding a record for example.com"); + Services.cookieBanners.markSiteExecuted("example.com", true, false); + + info("Verify that the record is properly added"); + Assert.ok( + Services.cookieBanners.shouldStopBannerClickingForSite( + "example.com", + true, + false + ), + "The record is properly set." + ); + + info("Trigger the deleteDataFromHost"); + await new Promise(resolve => { + Services.clearData.deleteDataFromHost( + "example.com", + true /* user request */, + Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD, + _ => { + resolve(); + } + ); + }); + + info("Verify that the exception is deleted"); + Assert.ok( + !Services.cookieBanners.shouldStopBannerClickingForSite( + "example.com", + true, + false + ), + "The record is properly cleared." + ); + + // Test nsIClearDataService.deleteDataFromBaseDomain + info("Adding a record for example.com"); + Services.cookieBanners.markSiteExecuted("example.com", true, false); + + info("Verify that the record is properly added"); + Assert.ok( + Services.cookieBanners.shouldStopBannerClickingForSite( + "example.com", + true, + false + ), + "The record is properly set." + ); + + info("Trigger the deleteDataFromBaseDomain"); + await new Promise(resolve => { + Services.clearData.deleteDataFromBaseDomain( + "example.com", + true /* user request */, + Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD, + _ => { + resolve(); + } + ); + }); + + info("Verify that the exception is deleted"); + Assert.ok( + !Services.cookieBanners.shouldStopBannerClickingForSite( + "example.com", + true, + false + ), + "The record is properly cleared." + ); + + // Test nsIClearDataService.deleteDataFromPrincipal + info("Adding a record for example.com"); + Services.cookieBanners.markSiteExecuted("example.com", true, false); + + info("Verify that the record is properly added"); + Assert.ok( + Services.cookieBanners.shouldStopBannerClickingForSite( + "example.com", + true, + false + ), + "The record is properly set." + ); + + info("Trigger the deleteDataFromPrincipal"); + let uri = Services.io.newURI("https://example.com"); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + await new Promise(resolve => { + Services.clearData.deleteDataFromPrincipal( + principal, + true /* user request */, + Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD, + _ => { + resolve(); + } + ); + }); + + info("Verify that the exception is deleted"); + Assert.ok( + !Services.cookieBanners.shouldStopBannerClickingForSite( + "example.com", + true, + false + ), + "The record is properly cleared." + ); + + // Test nsIClearDataService.deleteData + info("Adding a record for example.com"); + Services.cookieBanners.markSiteExecuted("example.com", true, false); + + info("Verify that the record is properly added"); + Assert.ok( + Services.cookieBanners.shouldStopBannerClickingForSite( + "example.com", + true, + false + ), + "The record is properly set." + ); + + info("Trigger the deleteData"); + await new Promise(resolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD, + _ => { + resolve(); + } + ); + }); + + info("Verify that the exception is deleted"); + Assert.ok( + !Services.cookieBanners.shouldStopBannerClickingForSite( + "example.com", + true, + false + ), + "The record is properly cleared." + ); + + Services.prefs.clearUserPref("cookiebanners.service.mode"); + Services.prefs.clearUserPref( + "cookiebanners.bannerClicking.maxTriesPerSiteAndSession" + ); +}); diff --git a/toolkit/components/cleardata/tests/unit/test_cookies.js b/toolkit/components/cleardata/tests/unit/test_cookies.js new file mode 100644 index 0000000000..4bcb6d725a --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_cookies.js @@ -0,0 +1,393 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests for cookies. + */ + +"use strict"; + +add_task(async function test_all_cookies() { + const expiry = Date.now() + 24 * 60 * 60; + Services.cookies.add( + "example.net", + "path", + "name", + "value", + true /* secure */, + true /* http only */, + false /* session */, + expiry, + {}, + Ci.nsICookie.SAMESITE_NONE, + Ci.nsICookie.SCHEME_HTTPS + ); + Assert.equal(Services.cookies.countCookiesFromHost("example.net"), 1); + + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_COOKIES, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.equal(Services.cookies.countCookiesFromHost("example.net"), 0); +}); + +add_task(async function test_range_cookies() { + const expiry = Date.now() + 24 * 60 * 60; + Services.cookies.add( + "example.net", + "path", + "name", + "value", + true /* secure */, + true /* http only */, + false /* session */, + expiry, + {}, + Ci.nsICookie.SAMESITE_NONE, + Ci.nsICookie.SCHEME_HTTPS + ); + Assert.equal(Services.cookies.countCookiesFromHost("example.net"), 1); + + // The cookie is out of time range here. + let from = Date.now() + 60 * 60; + await new Promise(aResolve => { + Services.clearData.deleteDataInTimeRange( + from * 1000, + expiry * 2000, + true /* user request */, + Ci.nsIClearDataService.CLEAR_COOKIES, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.equal(Services.cookies.countCookiesFromHost("example.net"), 1); + + // Now we delete all. + from = Date.now() - 60 * 60; + await new Promise(aResolve => { + Services.clearData.deleteDataInTimeRange( + from * 1000, + expiry * 2000, + true /* user request */, + Ci.nsIClearDataService.CLEAR_COOKIES, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.equal(Services.cookies.countCookiesFromHost("example.net"), 0); +}); + +add_task(async function test_principal_cookies() { + const expiry = Date.now() + 24 * 60 * 60; + Services.cookies.add( + "example.net", + "path", + "name", + "value", + true /* secure */, + true /* http only */, + false /* session */, + expiry, + {}, + Ci.nsICookie.SAMESITE_NONE, + Ci.nsICookie.SCHEME_HTTPS + ); + Assert.equal(Services.cookies.countCookiesFromHost("example.net"), 1); + + let uri = Services.io.newURI("http://example.com"); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + await new Promise(aResolve => { + Services.clearData.deleteDataFromPrincipal( + principal, + true /* user request */, + Ci.nsIClearDataService.CLEAR_COOKIES, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.equal(Services.cookies.countCookiesFromHost("example.net"), 1); + + // Now we delete all. + uri = Services.io.newURI("http://example.net"); + principal = Services.scriptSecurityManager.createContentPrincipal(uri, {}); + await new Promise(aResolve => { + Services.clearData.deleteDataFromPrincipal( + principal, + true /* user request */, + Ci.nsIClearDataService.CLEAR_COOKIES, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.equal(Services.cookies.countCookiesFromHost("example.net"), 0); +}); + +add_task(async function test_localfile_cookies() { + const expiry = Date.now() + 24 * 60 * 60; + Services.cookies.add( + "", // local file + "path", + "name", + "value", + false /* secure */, + false /* http only */, + false /* session */, + expiry, + {}, + Ci.nsICookie.SAMESITE_NONE, + Ci.nsICookie.SCHEME_HTTP + ); + + Assert.notEqual(Services.cookies.countCookiesFromHost(""), 0); + + await new Promise(aResolve => { + Services.clearData.deleteDataFromLocalFiles( + true, + Ci.nsIClearDataService.CLEAR_COOKIES, + aResolve + ); + }); + Assert.equal(Services.cookies.countCookiesFromHost(""), 0); +}); + +// The following tests ensure we properly clear (partitioned/unpartitioned) +// cookies when using deleteDataFromBaseDomain and deleteDataFromHost. + +function getTestCookieName(host, topLevelBaseDomain) { + if (!topLevelBaseDomain) { + return host; + } + return `${host}_${topLevelBaseDomain}`; +} + +function setTestCookie({ + host, + topLevelBaseDomain = null, + originAttributes = {}, +}) { + SiteDataTestUtils.addToCookies({ + host, + name: getTestCookieName(host, topLevelBaseDomain), + originAttributes: getOAWithPartitionKey( + { topLevelBaseDomain }, + originAttributes + ), + }); +} + +function setTestCookies() { + // First party cookies + setTestCookie({ host: "example.net" }); + setTestCookie({ host: "test.example.net" }); + setTestCookie({ host: "example.org" }); + + // Third-party partitioned cookies. + setTestCookie({ host: "example.com", topLevelBaseDomain: "example.net" }); + setTestCookie({ + host: "example.com", + topLevelBaseDomain: "example.net", + originAttributes: { userContextId: 1 }, + }); + setTestCookie({ host: "example.net", topLevelBaseDomain: "example.org" }); + setTestCookie({ + host: "test.example.net", + topLevelBaseDomain: "example.org", + }); + + // Ensure we have the correct cookie test state. + // Not using countCookiesFromHost because it doesn't see partitioned cookies. + testCookieExists({ host: "example.net" }); + testCookieExists({ host: "test.example.net" }); + testCookieExists({ host: "example.org" }); + + testCookieExists({ host: "example.com", topLevelBaseDomain: "example.net" }); + testCookieExists({ + host: "example.com", + topLevelBaseDomain: "example.net", + originAttributes: { userContextId: 1 }, + }); + testCookieExists({ host: "example.net", topLevelBaseDomain: "example.org" }); + testCookieExists({ + host: "test.example.net", + topLevelBaseDomain: "example.org", + }); +} + +function testCookieExists({ + host, + topLevelBaseDomain = null, + expected = true, + originAttributes = {}, +}) { + let exists = Services.cookies.cookieExists( + host, + "path", + getTestCookieName(host, topLevelBaseDomain), + getOAWithPartitionKey({ topLevelBaseDomain }, originAttributes) + ); + let message = `Cookie ${expected ? "is set" : "is not set"} for ${host}`; + if (topLevelBaseDomain) { + message += ` partitioned under ${topLevelBaseDomain}`; + } + Assert.equal(exists, expected, message); + return exists; +} + +/** + * Tests deleting (partitioned) cookies by base domain. + */ +add_task(async function test_baseDomain_cookies() { + Services.cookies.removeAll(); + setTestCookies(); + + // Clear cookies of example.net including partitions. + await new Promise(aResolve => { + Services.clearData.deleteDataFromBaseDomain( + "example.net", + false, + Ci.nsIClearDataService.CLEAR_COOKIES, + aResolve + ); + }); + + testCookieExists({ host: "example.net", expected: false }); + testCookieExists({ host: "test.example.net", expected: false }); + testCookieExists({ host: "example.org" }); + + testCookieExists({ + host: "example.com", + topLevelBaseDomain: "example.net", + expected: false, + }); + testCookieExists({ + host: "example.com", + topLevelBaseDomain: "example.net", + originAttributes: { userContextId: 1 }, + expected: false, + }); + testCookieExists({ + host: "example.net", + topLevelBaseDomain: "example.org", + expected: false, + }); + testCookieExists({ + host: "test.example.net", + topLevelBaseDomain: "example.org", + expected: false, + }); + + // Cleanup + Services.cookies.removeAll(); +}); + +/** + * Tests deleting (non-partitioned) cookies by host. + */ +add_task(async function test_host_cookies() { + Services.cookies.removeAll(); + setTestCookies(); + + // Clear cookies of example.net without partitions. + await new Promise(aResolve => { + Services.clearData.deleteDataFromHost( + "example.net", + false, + Ci.nsIClearDataService.CLEAR_COOKIES, + aResolve + ); + }); + + testCookieExists({ host: "example.net", expected: false }); + testCookieExists({ host: "test.example.net" }); + testCookieExists({ host: "example.org" }); + // Third-party partitioned cookies under example.net should not be cleared. + testCookieExists({ host: "example.com", topLevelBaseDomain: "example.net" }); + setTestCookie({ + host: "example.com", + topLevelBaseDomain: "example.net", + originAttributes: { userContextId: 1 }, + }); + // Third-party partitioned cookies of example.net should be removed, because + // CookieCleaner matches with host, but any partition key (oa = {}) via + // removeCookiesFromExactHost. + testCookieExists({ + host: "example.net", + topLevelBaseDomain: "example.org", + expected: false, + }); + testCookieExists({ + host: "test.example.net", + topLevelBaseDomain: "example.org", + }); + + // Cleanup + Services.cookies.removeAll(); +}); + +/** + * Tests that we correctly clear data when given a subdomain. + */ +add_task(async function test_baseDomain_cookies_subdomain() { + Services.cookies.removeAll(); + setTestCookies(); + + // Clear cookies of test.example.net including partitions. + await new Promise(aResolve => { + Services.clearData.deleteDataFromBaseDomain( + "test.example.net", + false, + Ci.nsIClearDataService.CLEAR_COOKIES, + aResolve + ); + }); + + testCookieExists({ host: "example.net", expected: false }); + testCookieExists({ host: "test.example.net", expected: false }); + testCookieExists({ host: "example.org" }); + + testCookieExists({ + host: "example.com", + topLevelBaseDomain: "example.net", + expected: false, + }); + setTestCookie({ + host: "example.com", + topLevelBaseDomain: "example.net", + originAttributes: { userContextId: 1 }, + expected: false, + }); + testCookieExists({ + host: "example.net", + topLevelBaseDomain: "example.org", + expected: false, + }); + testCookieExists({ + host: "test.example.net", + topLevelBaseDomain: "example.org", + expected: false, + }); + + // Cleanup + Services.cookies.removeAll(); +}); diff --git a/toolkit/components/cleardata/tests/unit/test_downloads.js b/toolkit/components/cleardata/tests/unit/test_downloads.js new file mode 100644 index 0000000000..72de763ce3 --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_downloads.js @@ -0,0 +1,310 @@ +/** + * Tests for downloads. + */ + +"use strict"; + +const { Downloads } = ChromeUtils.importESModule( + "resource://gre/modules/Downloads.sys.mjs" +); +const { FileTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/FileTestUtils.sys.mjs" +); + +const TEST_TARGET_FILE_NAME = "test-download.txt"; +let fileURL; +let downloadList; + +function createFileURL() { + if (!fileURL) { + const file = Services.dirsvc.get("TmpD", Ci.nsIFile); + file.append("foo.txt"); + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + + fileURL = Services.io.newFileURI(file); + } + + return fileURL; +} + +async function createDownloadList() { + if (!downloadList) { + Downloads._promiseListsInitialized = null; + Downloads._lists = {}; + Downloads._summaries = {}; + + downloadList = await Downloads.getList(Downloads.ALL); + } + + return downloadList; +} + +add_task(async function test_all_downloads() { + const url = createFileURL(); + const list = await createDownloadList(); + + // First download. + let download = await Downloads.createDownload({ + source: { url: url.spec, isPrivate: false }, + target: { path: FileTestUtils.getTempFile(TEST_TARGET_FILE_NAME).path }, + }); + Assert.ok(!!download); + list.add(download); + + let view; + let removePromise = new Promise(resolve => { + view = { + onDownloadAdded() {}, + onDownloadChanged() {}, + onDownloadRemoved() { + resolve(); + }, + }; + }); + + await list.addView(view); + + let items = await list.getAll(); + Assert.equal(items.length, 1); + + await new Promise(resolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOWNLOADS, + value => { + Assert.equal(value, 0); + resolve(); + } + ); + }); + + await removePromise; + + items = await list.getAll(); + Assert.equal(items.length, 0); +}); + +add_task(async function test_range_downloads() { + const url = createFileURL(); + const list = await createDownloadList(); + + let download = await Downloads.createDownload({ + source: { url: url.spec, isPrivate: false }, + target: { path: FileTestUtils.getTempFile(TEST_TARGET_FILE_NAME).path }, + }); + Assert.ok(!!download); + list.add(download); + + // Start + cancel. I need to have a startTime value. + await download.start(); + await download.cancel(); + + let items = await list.getAll(); + Assert.equal(items.length, 1); + + let view; + let removePromise = new Promise(resolve => { + view = { + onDownloadAdded() {}, + onDownloadChanged() {}, + onDownloadRemoved() { + resolve(); + }, + }; + }); + + await list.addView(view); + + await new Promise(resolve => { + Services.clearData.deleteDataInTimeRange( + download.startTime.getTime() * 1000, + download.startTime.getTime() * 1000, + true /* user request */, + Ci.nsIClearDataService.CLEAR_DOWNLOADS, + value => { + Assert.equal(value, 0); + resolve(); + } + ); + }); + + await removePromise; + + items = await list.getAll(); + Assert.equal(items.length, 0); +}); + +add_task(async function test_principal_downloads() { + const list = await createDownloadList(); + + let download = await Downloads.createDownload({ + source: { url: "http://example.net", isPrivate: false }, + target: { path: FileTestUtils.getTempFile(TEST_TARGET_FILE_NAME).path }, + }); + Assert.ok(!!download); + list.add(download); + + download = await Downloads.createDownload({ + source: { url: "http://example.com", isPrivate: false }, + target: { path: FileTestUtils.getTempFile(TEST_TARGET_FILE_NAME).path }, + }); + Assert.ok(!!download); + list.add(download); + + let items = await list.getAll(); + Assert.equal(items.length, 2); + + let view; + let removePromise = new Promise(resolve => { + view = { + onDownloadAdded() {}, + onDownloadChanged() {}, + onDownloadRemoved() { + resolve(); + }, + }; + }); + + await list.addView(view); + + let uri = Services.io.newURI("http://example.com"); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + + await new Promise(resolve => { + Services.clearData.deleteDataFromPrincipal( + principal, + true /* user request */, + Ci.nsIClearDataService.CLEAR_DOWNLOADS, + value => { + Assert.equal(value, 0); + resolve(); + } + ); + }); + + await removePromise; + + items = await list.getAll(); + Assert.equal(items.length, 1); + + removePromise = new Promise(resolve => { + view = { + onDownloadAdded() {}, + onDownloadChanged() {}, + onDownloadRemoved() { + resolve(); + }, + }; + }); + + await list.addView(view); + + await new Promise(resolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOWNLOADS, + value => { + Assert.equal(value, 0); + resolve(); + } + ); + }); + + await removePromise; + + items = await list.getAll(); + Assert.equal(items.length, 0); +}); + +add_task(async function test_basedomain_downloads() { + const list = await createDownloadList(); + + let download = await Downloads.createDownload({ + source: { url: "http://example.net", isPrivate: false }, + target: { path: FileTestUtils.getTempFile(TEST_TARGET_FILE_NAME).path }, + }); + Assert.ok(!!download); + list.add(download); + + download = await Downloads.createDownload({ + source: { url: "http://test.example.net", isPrivate: false }, + target: { path: FileTestUtils.getTempFile(TEST_TARGET_FILE_NAME).path }, + }); + Assert.ok(!!download); + list.add(download); + + download = await Downloads.createDownload({ + source: { url: "https://foo.bar.example.net", isPrivate: true }, + target: { path: FileTestUtils.getTempFile(TEST_TARGET_FILE_NAME).path }, + }); + Assert.ok(!!download); + list.add(download); + + download = await Downloads.createDownload({ + source: { url: "http://example.com", isPrivate: false }, + target: { path: FileTestUtils.getTempFile(TEST_TARGET_FILE_NAME).path }, + }); + Assert.ok(!!download); + list.add(download); + + let items = await list.getAll(); + Assert.equal(items.length, 4); + + let view; + let removePromise = new Promise(resolve => { + view = { + onDownloadAdded() {}, + onDownloadChanged() {}, + onDownloadRemoved() { + resolve(); + }, + }; + }); + + await list.addView(view); + + await new Promise(resolve => { + Services.clearData.deleteDataFromBaseDomain( + "example.net", + true /* user request */, + Ci.nsIClearDataService.CLEAR_DOWNLOADS, + value => { + Assert.equal(value, 0); + resolve(); + } + ); + }); + + await removePromise; + + items = await list.getAll(); + Assert.equal(items.length, 1); + + removePromise = new Promise(resolve => { + view = { + onDownloadAdded() {}, + onDownloadChanged() {}, + onDownloadRemoved() { + resolve(); + }, + }; + }); + + await list.addView(view); + + await new Promise(resolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOWNLOADS, + value => { + Assert.equal(value, 0); + resolve(); + } + ); + }); + + await removePromise; + + items = await list.getAll(); + Assert.equal(items.length, 0); +}); diff --git a/toolkit/components/cleardata/tests/unit/test_fingerprinting_protection_state.js b/toolkit/components/cleardata/tests/unit/test_fingerprinting_protection_state.js new file mode 100644 index 0000000000..6c50f91551 --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_fingerprinting_protection_state.js @@ -0,0 +1,161 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +do_get_profile(); + +add_task(async function test_clear_fingerprinting_protection_state() { + info("Enabling fingerprinting randomization"); + Services.prefs.setBoolPref("privacy.resistFingerprinting", true); + + let uri = Services.io.newURI("https://example.com"); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + let channel = Services.io.newChannelFromURI( + uri, + null, // aLoadingNode + principal, + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + Ci.nsIContentPolicy.TYPE_DOCUMENT + ); + + // Test nsIClearDataService.deleteDataFromHost + let key = Services.rfp.testGenerateRandomKey(channel); + let keyStr = key.map(bytes => bytes.toString(16).padStart(2, "0")).join(""); + + // Verify that the key remains the same without clearing. + key = Services.rfp.testGenerateRandomKey(channel); + let keyStrAgain = key + .map(bytes => bytes.toString(16).padStart(2, "0")) + .join(""); + + Assert.equal( + keyStr, + keyStrAgain, + "The fingerprinting randomization key remain the same without clearing." + ); + + info("Trigger the deleteDataFromHost"); + await new Promise(resolve => { + Services.clearData.deleteDataFromHost( + "example.com", + true /* user request */, + Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE, + _ => { + resolve(); + } + ); + }); + + key = Services.rfp.testGenerateRandomKey(channel); + let newKeyStr = key + .map(bytes => bytes.toString(16).padStart(2, "0")) + .join(""); + + Assert.notEqual( + keyStr, + newKeyStr, + "The fingerprinting randomization key is reset properly." + ); + + // Test nsIClearDataService.deleteDataFromBaseDomain + keyStr = newKeyStr; + + info("Trigger the deleteDataFromBaseDomain"); + await new Promise(resolve => { + Services.clearData.deleteDataFromBaseDomain( + "example.com", + true /* user request */, + Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE, + _ => { + resolve(); + } + ); + }); + + key = Services.rfp.testGenerateRandomKey(channel); + newKeyStr = key.map(bytes => bytes.toString(16).padStart(2, "0")).join(""); + + Assert.notEqual( + keyStr, + newKeyStr, + "The fingerprinting randomization key is reset properly." + ); + + // Test nsIClearDataService.deleteDataFromPrincipal + keyStr = newKeyStr; + + await new Promise(resolve => { + Services.clearData.deleteDataFromPrincipal( + principal, + true /* user request */, + Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE, + _ => { + resolve(); + } + ); + }); + + key = Services.rfp.testGenerateRandomKey(channel); + newKeyStr = key.map(bytes => bytes.toString(16).padStart(2, "0")).join(""); + + Assert.notEqual( + keyStr, + newKeyStr, + "The fingerprinting randomization key is reset properly." + ); + + // Test nsIClearDataService.deleteData + keyStr = newKeyStr; + + // Generate a key for another site. + uri = Services.io.newURI("https://example.org"); + principal = Services.scriptSecurityManager.createContentPrincipal(uri, {}); + let channelAnother = Services.io.newChannelFromURI( + uri, + null, // aLoadingNode + principal, + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + Ci.nsIContentPolicy.TYPE_DOCUMENT + ); + key = Services.rfp.testGenerateRandomKey(channelAnother); + let keyStrAnother = key + .map(bytes => bytes.toString(16).padStart(2, "0")) + .join(""); + + info("Trigger the deleteData"); + await new Promise(resolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE, + _ => { + resolve(); + } + ); + }); + + key = Services.rfp.testGenerateRandomKey(channel); + newKeyStr = key.map(bytes => bytes.toString(16).padStart(2, "0")).join(""); + + Assert.notEqual( + keyStr, + newKeyStr, + "The fingerprinting randomization key is reset properly." + ); + + // Verify whether deleteData clears another site as well. + key = Services.rfp.testGenerateRandomKey(channelAnother); + newKeyStr = key.map(bytes => bytes.toString(16).padStart(2, "0")).join(""); + + Assert.notEqual( + keyStrAnother, + newKeyStr, + "The fingerprinting randomization key is reset properly for another site." + ); + + Services.prefs.clearUserPref("privacy.resistFingerprinting"); +}); diff --git a/toolkit/components/cleardata/tests/unit/test_identity_credential_storage.js b/toolkit/components/cleardata/tests/unit/test_identity_credential_storage.js new file mode 100644 index 0000000000..13369fc787 --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_identity_credential_storage.js @@ -0,0 +1,121 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "IdentityCredentialStorageService", + "@mozilla.org/browser/identity-credential-storage-service;1", + "nsIIdentityCredentialStorageService" +); + +do_get_profile(); + +add_task(async function test_deleteByRange() { + Services.prefs.setBoolPref( + "dom.security.credentialmanagement.identity.enabled", + true + ); + const expiry = Date.now() + 24 * 60 * 60; + let rpPrincipal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://rp.com/"), + {} + ); + let idpPrincipal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("https://idp.com/"), + {} + ); + const credentialID = "ID"; + + // Test initial value + let registered = {}; + let allowLogout = {}; + IdentityCredentialStorageService.getState( + rpPrincipal, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + Assert.ok(!registered.value, "Should not be registered initially."); + Assert.ok(!allowLogout.value, "Should not allow logout initially."); + + // Set and read a value + IdentityCredentialStorageService.setState( + rpPrincipal, + idpPrincipal, + credentialID, + true, + true + ); + + IdentityCredentialStorageService.getState( + rpPrincipal, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + Assert.ok(registered.value, "Should be registered by set."); + Assert.ok(allowLogout.value, "Should now allow logout by set."); + + let from = Date.now() + 60 * 60; + await new Promise(aResolve => { + Services.clearData.deleteDataInTimeRange( + from * 1000, + expiry * 1000, + true /* user request */, + Ci.nsIClearDataService.CLEAR_CREDENTIAL_MANAGER_STATE, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + IdentityCredentialStorageService.getState( + rpPrincipal, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + + Assert.ok( + registered.value, + "Should be existing since the value is not deleted" + ); + + from = Date.now() - 60 * 60; + + await new Promise(aResolve => { + Services.clearData.deleteDataInTimeRange( + from * 1000, + expiry * 1000, + true /* user request */, + Ci.nsIClearDataService.CLEAR_CREDENTIAL_MANAGER_STATE, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + IdentityCredentialStorageService.getState( + rpPrincipal, + idpPrincipal, + credentialID, + registered, + allowLogout + ); + Assert.ok(!registered.value, "Should not be existing"); + + Services.prefs.clearUserPref( + "dom.security.credentialmanagement.identity.enabled" + ); +}); diff --git a/toolkit/components/cleardata/tests/unit/test_network_cache.js b/toolkit/components/cleardata/tests/unit/test_network_cache.js new file mode 100644 index 0000000000..bb54cdc6a8 --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_network_cache.js @@ -0,0 +1,316 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test clearing cache. + */ + +"use strict"; + +function getPartitionedLoadContextInfo( + { scheme, topLevelBaseDomain, port }, + originAttributes = {} +) { + return Services.loadContextInfo.custom( + false, + getOAWithPartitionKey( + { scheme, topLevelBaseDomain, port }, + originAttributes + ) + ); +} + +add_task(async function test_deleteFromHost() { + await SiteDataTestUtils.addCacheEntry("http://example.com/", "disk"); + await SiteDataTestUtils.addCacheEntry("http://example.com/", "memory"); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.com/", "disk"), + "The disk cache has an entry" + ); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.com/", "memory"), + "The memory cache has an entry" + ); + + await SiteDataTestUtils.addCacheEntry("http://example.org/", "disk"); + await SiteDataTestUtils.addCacheEntry("http://example.org/", "memory"); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.org/", "disk"), + "The disk cache has an entry" + ); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.org/", "memory"), + "The memory cache has an entry" + ); + + await new Promise(aResolve => { + Services.clearData.deleteDataFromHost( + "example.com", + true, + Ci.nsIClearDataService.CLEAR_NETWORK_CACHE, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.ok( + !SiteDataTestUtils.hasCacheEntry("http://example.com/", "disk"), + "The disk cache is cleared" + ); + Assert.ok( + !SiteDataTestUtils.hasCacheEntry("http://example.com/", "memory"), + "The memory cache is cleared" + ); + + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.org/", "disk"), + "The disk cache has an entry" + ); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.org/", "memory"), + "The memory cache has an entry" + ); + + await SiteDataTestUtils.clear(); +}); + +add_task(async function test_deleteFromPrincipal() { + await SiteDataTestUtils.addCacheEntry("http://example.com/", "disk"); + await SiteDataTestUtils.addCacheEntry("http://example.com/", "memory"); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.com/", "disk"), + "The disk cache has an entry" + ); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.com/", "memory"), + "The memory cache has an entry" + ); + + await SiteDataTestUtils.addCacheEntry("http://example.org/", "disk"); + await SiteDataTestUtils.addCacheEntry("http://example.org/", "memory"); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.org/", "disk"), + "The disk cache has an entry" + ); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.org/", "memory"), + "The memory cache has an entry" + ); + + let principal = + Services.scriptSecurityManager.createContentPrincipalFromOrigin( + "http://example.com/" + ); + await new Promise(aResolve => { + Services.clearData.deleteDataFromPrincipal( + principal, + true, + Ci.nsIClearDataService.CLEAR_NETWORK_CACHE, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.ok( + !SiteDataTestUtils.hasCacheEntry("http://example.com/", "disk"), + "The disk cache is cleared" + ); + Assert.ok( + !SiteDataTestUtils.hasCacheEntry("http://example.com/", "memory"), + "The memory cache is cleared" + ); + + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.org/", "disk"), + "The disk cache has an entry" + ); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.org/", "memory"), + "The memory cache has an entry" + ); + + await SiteDataTestUtils.clear(); +}); + +add_task(async function test_deleteFromBaseDomain() { + for (let cacheType of ["disk", "memory"]) { + await SiteDataTestUtils.addCacheEntry("http://example.com/", cacheType); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.com/", cacheType), + `The ${cacheType} cache has an entry.` + ); + + await SiteDataTestUtils.addCacheEntry("http://example.org/", cacheType); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.org/", cacheType), + `The ${cacheType} cache has an entry.` + ); + + // Partitioned cache. + await SiteDataTestUtils.addCacheEntry( + "http://example.com/", + cacheType, + getPartitionedLoadContextInfo({ topLevelBaseDomain: "example.org" }) + ); + Assert.ok( + SiteDataTestUtils.hasCacheEntry( + "http://example.com/", + cacheType, + getPartitionedLoadContextInfo({ topLevelBaseDomain: "example.org" }) + ), + `The ${cacheType} cache has a partitioned entry` + ); + await SiteDataTestUtils.addCacheEntry( + "http://example.org/", + cacheType, + getPartitionedLoadContextInfo({ topLevelBaseDomain: "example.com" }) + ); + Assert.ok( + SiteDataTestUtils.hasCacheEntry( + "http://example.org/", + cacheType, + getPartitionedLoadContextInfo({ topLevelBaseDomain: "example.com" }) + ), + `The ${cacheType} cache has a partitioned entry` + ); + + // Clear an unrelated base domain. + await new Promise(aResolve => { + Services.clearData.deleteDataFromBaseDomain( + "foo.com", + true, + Ci.nsIClearDataService.CLEAR_NETWORK_CACHE, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + // Should still have all cache entries. + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.com/", cacheType), + `The ${cacheType} cache has an entry.` + ); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.org/", cacheType), + `The ${cacheType} cache has an entry.` + ); + Assert.ok( + SiteDataTestUtils.hasCacheEntry( + "http://example.com/", + cacheType, + getPartitionedLoadContextInfo({ topLevelBaseDomain: "example.org" }) + ), + `The ${cacheType} cache has a partitioned entry` + ); + Assert.ok( + SiteDataTestUtils.hasCacheEntry( + "http://example.org/", + cacheType, + getPartitionedLoadContextInfo({ topLevelBaseDomain: "example.com" }) + ), + `The ${cacheType} cache has a partitioned entry` + ); + + // Clear data for example.com + await new Promise(aResolve => { + Services.clearData.deleteDataFromBaseDomain( + "example.com", + true, + Ci.nsIClearDataService.CLEAR_NETWORK_CACHE, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.ok( + !SiteDataTestUtils.hasCacheEntry("http://example.com/", cacheType), + `The ${cacheType} cache is cleared.` + ); + + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.org/", cacheType), + `The ${cacheType} cache has an entry.` + ); + + Assert.ok( + !SiteDataTestUtils.hasCacheEntry( + "http://example.com/", + cacheType, + getPartitionedLoadContextInfo({ topLevelBaseDomain: "example.org" }) + ), + `The ${cacheType} cache is cleared.` + ); + + Assert.ok( + !SiteDataTestUtils.hasCacheEntry( + "http://example.org/", + cacheType, + getPartitionedLoadContextInfo({ topLevelBaseDomain: "example.com" }) + ), + `The ${cacheType} cache is cleared.` + ); + await SiteDataTestUtils.clear(); + } +}); + +add_task(async function test_deleteAll() { + await SiteDataTestUtils.addCacheEntry("http://example.com/", "disk"); + await SiteDataTestUtils.addCacheEntry("http://example.com/", "memory"); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.com/", "disk"), + "The disk cache has an entry" + ); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.com/", "memory"), + "The memory cache has an entry" + ); + + await SiteDataTestUtils.addCacheEntry("http://example.org/", "disk"); + await SiteDataTestUtils.addCacheEntry("http://example.org/", "memory"); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.org/", "disk"), + "The disk cache has an entry" + ); + Assert.ok( + SiteDataTestUtils.hasCacheEntry("http://example.org/", "memory"), + "The memory cache has an entry" + ); + + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_NETWORK_CACHE, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.ok( + !SiteDataTestUtils.hasCacheEntry("http://example.com/", "disk"), + "The disk cache is cleared" + ); + Assert.ok( + !SiteDataTestUtils.hasCacheEntry("http://example.com/", "memory"), + "The memory cache is cleared" + ); + + Assert.ok( + !SiteDataTestUtils.hasCacheEntry("http://example.org/", "disk"), + "The disk cache is cleared" + ); + Assert.ok( + !SiteDataTestUtils.hasCacheEntry("http://example.org/", "memory"), + "The memory cache is cleared" + ); + + await SiteDataTestUtils.clear(); +}); diff --git a/toolkit/components/cleardata/tests/unit/test_passwords.js b/toolkit/components/cleardata/tests/unit/test_passwords.js new file mode 100644 index 0000000000..7e63bd8fa3 --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_passwords.js @@ -0,0 +1,89 @@ +/** + * Tests for passwords. + */ + +"use strict"; + +const URL = "http://example.com"; + +const { LoginTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/LoginTestUtils.sys.mjs" +); + +add_task(async function test_principal_downloads() { + // Store the strings "user" and "pass" using similarly looking glyphs. + let loginInfo = LoginTestUtils.testData.formLogin({ + origin: URL, + formActionOrigin: URL, + username: "admin", + password: "12345678", + usernameField: "field_username", + passwordField: "field_password", + }); + await Services.logins.addLoginAsync(loginInfo); + + Assert.equal(await countLogins(URL), 1); + + let uri = Services.io.newURI(URL); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + + await new Promise(resolve => { + Services.clearData.deleteDataFromPrincipal( + principal, + true /* user request */, + Ci.nsIClearDataService.CLEAR_PASSWORDS, + value => { + Assert.equal(value, 0); + resolve(); + } + ); + }); + + Assert.equal(await countLogins(URL), 0); + + LoginTestUtils.clearData(); +}); + +add_task(async function test_all() { + // Store the strings "user" and "pass" using similarly looking glyphs. + let loginInfo = LoginTestUtils.testData.formLogin({ + origin: URL, + formActionOrigin: URL, + username: "admin", + password: "12345678", + usernameField: "field_username", + passwordField: "field_password", + }); + await Services.logins.addLoginAsync(loginInfo); + + Assert.equal(await countLogins(URL), 1); + + await new Promise(resolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_PASSWORDS, + value => { + Assert.equal(value, 0); + resolve(); + } + ); + }); + + Assert.equal(await countLogins(URL), 0); + + LoginTestUtils.clearData(); +}); + +async function countLogins(origin) { + let count = 0; + const logins = await Services.logins.getAllLogins(); + for (const login of logins) { + if (login.origin == origin) { + ++count; + } + } + + return count; +} diff --git a/toolkit/components/cleardata/tests/unit/test_permissions.js b/toolkit/components/cleardata/tests/unit/test_permissions.js new file mode 100644 index 0000000000..1f46ab5015 --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_permissions.js @@ -0,0 +1,471 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests for permissions + */ + +"use strict"; + +add_task(async function test_all_permissions() { + const uri = Services.io.newURI("https://example.net"); + const principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + + Services.perms.addFromPrincipal( + principal, + "cookie", + Services.perms.ALLOW_ACTION + ); + Assert.ok( + Services.perms.getPermissionObject(principal, "cookie", true) != null + ); + + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_PERMISSIONS, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.ok( + Services.perms.getPermissionObject(principal, "cookie", true) == null + ); +}); + +add_task(async function test_principal_permissions() { + const uri = Services.io.newURI("https://example.net"); + const principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + + const anotherUri = Services.io.newURI("https://example.com"); + const anotherPrincipal = + Services.scriptSecurityManager.createContentPrincipal(anotherUri, {}); + + Services.perms.addFromPrincipal( + principal, + "cookie", + Services.perms.ALLOW_ACTION + ); + Services.perms.addFromPrincipal( + anotherPrincipal, + "cookie", + Services.perms.ALLOW_ACTION + ); + Assert.ok( + Services.perms.getPermissionObject(principal, "cookie", true) != null + ); + Assert.ok( + Services.perms.getPermissionObject(anotherPrincipal, "cookie", true) != null + ); + + await new Promise(aResolve => { + Services.clearData.deleteDataFromPrincipal( + principal, + true /* user request */, + Ci.nsIClearDataService.CLEAR_PERMISSIONS, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.ok( + Services.perms.getPermissionObject(principal, "cookie", true) == null + ); + Assert.ok( + Services.perms.getPermissionObject(anotherPrincipal, "cookie", true) != null + ); + + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_PERMISSIONS, + value => aResolve() + ); + }); +}); + +function addTestPermissions() { + Services.perms.removeAll(); + + PermissionTestUtils.add( + "https://example.net", + "geo", + Services.perms.ALLOW_ACTION + ); + PermissionTestUtils.add( + "http://example.net", + "cookie", + Services.perms.DENY_ACTION + ); + PermissionTestUtils.add( + "https://bar.example.net", + "geo", + Services.perms.ALLOW_ACTION + ); + PermissionTestUtils.add( + "https://foo.bar.example.net", + "geo", + Services.perms.ALLOW_ACTION + ); + PermissionTestUtils.add( + "https://example.com", + "3rdPartyStorage^https://example.net", + Services.perms.ALLOW_ACTION + ); + PermissionTestUtils.add( + "https://example.com", + "3rdPartyFrameStorage^https://example.net", + Services.perms.ALLOW_ACTION + ); + + PermissionTestUtils.add( + "https://example.com", + "cookie", + Services.perms.ALLOW_ACTION + ); + PermissionTestUtils.add( + "http://example.com", + "geo", + Services.perms.ALLOW_ACTION + ); + + Assert.equal( + PermissionTestUtils.getPermissionObject("https://example.net", "geo", true) + .capability, + Services.perms.ALLOW_ACTION + ); + Assert.equal( + PermissionTestUtils.getPermissionObject( + "http://example.net", + "cookie", + true + ).capability, + Services.perms.DENY_ACTION + ); + Assert.equal( + PermissionTestUtils.getPermissionObject( + "https://bar.example.net", + "geo", + true + ).capability, + Services.perms.ALLOW_ACTION + ); + Assert.equal( + PermissionTestUtils.getPermissionObject( + "https://foo.bar.example.net", + "geo", + true + ).capability, + Services.perms.ALLOW_ACTION + ); + Assert.equal( + PermissionTestUtils.getPermissionObject( + "https://example.com", + "3rdPartyStorage^https://example.net", + true + ).capability, + Services.perms.ALLOW_ACTION + ); + Assert.equal( + PermissionTestUtils.getPermissionObject( + "https://example.com", + "3rdPartyFrameStorage^https://example.net", + true + ).capability, + Services.perms.ALLOW_ACTION + ); + + Assert.equal( + PermissionTestUtils.getPermissionObject( + "https://example.com", + "cookie", + true + ).capability, + Services.perms.ALLOW_ACTION + ); + Assert.equal( + PermissionTestUtils.getPermissionObject("http://example.com", "geo", true) + .capability, + Services.perms.ALLOW_ACTION + ); +} + +add_task(async function test_basedomain_permissions() { + for (let domain of [ + "example.net", + "test.example.net", + "foo.bar.example.net", + ]) { + addTestPermissions(); + + await new Promise(aResolve => { + Services.clearData.deleteDataFromBaseDomain( + domain, + true /* user request */, + Ci.nsIClearDataService.CLEAR_PERMISSIONS, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + // Should have cleared all entries associated with the base domain. + Assert.ok( + !PermissionTestUtils.getPermissionObject( + "https://example.net", + "geo", + true + ) + ); + Assert.ok( + !PermissionTestUtils.getPermissionObject( + "http://example.net", + "cookie", + true + ) + ); + Assert.ok( + !PermissionTestUtils.getPermissionObject( + "https://bar.example.net", + "geo", + true + ) + ); + Assert.ok( + !PermissionTestUtils.getPermissionObject( + "https://foo.bar.example.net", + "geo", + true + ) + ); + Assert.ok( + !PermissionTestUtils.getPermissionObject( + "https://example.com", + "3rdPartyStorage^https://example.net", + true + ) + ); + Assert.ok( + !PermissionTestUtils.getPermissionObject( + "https://example.com", + "3rdPartyFrameStorage^https://example.net", + true + ) + ); + + // Unrelated entries should still exist. + Assert.equal( + PermissionTestUtils.getPermissionObject( + "https://example.com", + "cookie", + true + ).capability, + Services.perms.ALLOW_ACTION + ); + Assert.equal( + PermissionTestUtils.getPermissionObject("http://example.com", "geo", true) + .capability, + Services.perms.ALLOW_ACTION + ); + } + + Services.perms.removeAll(); +}); + +add_task(async function test_host_permissions() { + addTestPermissions(); + + await new Promise(aResolve => { + Services.clearData.deleteDataFromHost( + "bar.example.net", + true /* user request */, + Ci.nsIClearDataService.CLEAR_PERMISSIONS, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + // Should have cleared all entries associated with the host and its + // subdomains. + Assert.ok( + !PermissionTestUtils.getPermissionObject( + "https://bar.example.net", + "geo", + true + ) + ); + Assert.ok( + !PermissionTestUtils.getPermissionObject( + "https://foo.bar.example.net", + "geo", + true + ) + ); + + // Unrelated entries should still exist. + Assert.equal( + PermissionTestUtils.getPermissionObject("https://example.net", "geo", true) + .capability, + Services.perms.ALLOW_ACTION + ); + Assert.equal( + PermissionTestUtils.getPermissionObject( + "http://example.net", + "cookie", + true + ).capability, + Services.perms.DENY_ACTION + ); + Assert.equal( + PermissionTestUtils.getPermissionObject( + "https://example.com", + "3rdPartyStorage^https://example.net", + true + ).capability, + Services.perms.ALLOW_ACTION + ); + Assert.equal( + PermissionTestUtils.getPermissionObject( + "https://example.com", + "3rdPartyFrameStorage^https://example.net", + true + ).capability, + Services.perms.ALLOW_ACTION + ); + + Assert.equal( + PermissionTestUtils.getPermissionObject( + "https://example.com", + "cookie", + true + ).capability, + Services.perms.ALLOW_ACTION + ); + Assert.equal( + PermissionTestUtils.getPermissionObject("http://example.com", "geo", true) + .capability, + Services.perms.ALLOW_ACTION + ); + + Services.perms.removeAll(); +}); + +add_task(async function test_3rdpartystorage_permissions() { + const uri = Services.io.newURI("https://example.net"); + const principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + Services.perms.addFromPrincipal( + principal, + "cookie", + Services.perms.ALLOW_ACTION + ); + + const anotherUri = Services.io.newURI("https://example.com"); + const anotherPrincipal = + Services.scriptSecurityManager.createContentPrincipal(anotherUri, {}); + Services.perms.addFromPrincipal( + anotherPrincipal, + "cookie", + Services.perms.ALLOW_ACTION + ); + Services.perms.addFromPrincipal( + anotherPrincipal, + "3rdPartyStorage^https://example.net", + Services.perms.ALLOW_ACTION + ); + Services.perms.addFromPrincipal( + anotherPrincipal, + "3rdPartyFrameStorage^https://example.net", + Services.perms.ALLOW_ACTION + ); + + const oneMoreUri = Services.io.newURI("https://example.org"); + const oneMorePrincipal = + Services.scriptSecurityManager.createContentPrincipal(oneMoreUri, {}); + Services.perms.addFromPrincipal( + oneMorePrincipal, + "cookie", + Services.perms.ALLOW_ACTION + ); + + Assert.ok( + Services.perms.getPermissionObject(principal, "cookie", true) != null + ); + Assert.ok( + Services.perms.getPermissionObject(anotherPrincipal, "cookie", true) != null + ); + Assert.ok( + Services.perms.getPermissionObject( + anotherPrincipal, + "3rdPartyStorage^https://example.net", + true + ) != null + ); + Assert.ok( + Services.perms.getPermissionObject( + anotherPrincipal, + "3rdPartyFrameStorage^https://example.net", + true + ) != null + ); + Assert.ok( + Services.perms.getPermissionObject(oneMorePrincipal, "cookie", true) != null + ); + + await new Promise(aResolve => { + Services.clearData.deleteDataFromPrincipal( + principal, + true /* user request */, + Ci.nsIClearDataService.CLEAR_PERMISSIONS, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.ok( + Services.perms.getPermissionObject(principal, "cookie", true) == null + ); + Assert.ok( + Services.perms.getPermissionObject(anotherPrincipal, "cookie", true) != null + ); + Assert.ok( + Services.perms.getPermissionObject( + anotherPrincipal, + "3rdPartyStorage^https://example.net", + true + ) == null + ); + Assert.ok( + Services.perms.getPermissionObject( + anotherPrincipal, + "3rdPartyFrameStorage^https://example.net", + true + ) == null + ); + Assert.ok( + Services.perms.getPermissionObject(oneMorePrincipal, "cookie", true) != null + ); + + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_PERMISSIONS, + value => aResolve() + ); + }); +}); diff --git a/toolkit/components/cleardata/tests/unit/test_quota.js b/toolkit/components/cleardata/tests/unit/test_quota.js new file mode 100644 index 0000000000..95c3025781 --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_quota.js @@ -0,0 +1,537 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests for the QuotaCleaner. + */ + +"use strict"; + +// The following tests ensure we properly clear (partitioned/unpartitioned) +// localStorage and indexedDB when using deleteDataFromBaseDomain, +// deleteDataFromHost and deleteDataFromPrincipal. + +// Skip localStorage tests when using legacy localStorage. The legacy +// localStorage implementation does not support clearing data by principal. See +// Bug 1688221, Bug 1688665. +const skipLocalStorageTests = Services.prefs.getBoolPref( + "dom.storage.enable_unsupported_legacy_implementation" +); + +/** + * Create an origin with partitionKey. + * @param {String} host - Host portion of origin to create. + * @param {String} [topLevelBaseDomain] - Optional first party base domain to use for partitionKey. + * @param {Object} [originAttributes] - Optional object of origin attributes to + * set. If topLevelBaseDomain is passed, the partitionKey will be overwritten. + * @returns {String} Origin with suffix. + */ +function getOrigin(host, topLevelBaseDomain, originAttributes = {}) { + return getPrincipal(host, topLevelBaseDomain, originAttributes).origin; +} + +function getPrincipal(host, topLevelBaseDomain, originAttributes = {}) { + originAttributes = getOAWithPartitionKey( + { topLevelBaseDomain }, + originAttributes + ); + let principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI(`https://${host}`), + originAttributes + ); + return principal; +} + +function getTestEntryName(host, topLevelBaseDomain) { + if (!topLevelBaseDomain) { + return host; + } + return `${host}_${topLevelBaseDomain}`; +} + +function setTestEntry({ + storageType, + host, + topLevelBaseDomain = null, + originAttributes = {}, +}) { + let origin = getOrigin(host, topLevelBaseDomain, originAttributes); + if (storageType == "localStorage") { + SiteDataTestUtils.addToLocalStorage( + origin, + getTestEntryName(host, topLevelBaseDomain), + "bar" + ); + return; + } + SiteDataTestUtils.addToIndexedDB(origin); +} + +async function testEntryExists({ + storageType, + host, + topLevelBaseDomain = null, + expected = true, + originAttributes = {}, +}) { + let exists; + let origin = getOrigin(host, topLevelBaseDomain, originAttributes); + if (storageType == "localStorage") { + exists = SiteDataTestUtils.hasLocalStorage(origin, [ + { key: getTestEntryName(host, topLevelBaseDomain), value: "bar" }, + ]); + } else { + exists = await SiteDataTestUtils.hasIndexedDB(origin); + } + + let message = `${storageType} entry ${ + expected ? "is set" : "is not set" + } for ${host}`; + if (topLevelBaseDomain) { + message += ` partitioned under ${topLevelBaseDomain}`; + } + Assert.equal(exists, expected, message); + return exists; +} + +const TEST_ORIGINS = [ + // First party + { host: "example.net" }, + { host: "test.example.net" }, + { host: "example.org" }, + + // Third-party partitioned. + { host: "example.com", topLevelBaseDomain: "example.net" }, + { + host: "example.com", + topLevelBaseDomain: "example.net", + originAttributes: { userContextId: 1 }, + }, + { host: "example.net", topLevelBaseDomain: "example.org" }, + { host: "test.example.net", topLevelBaseDomain: "example.org" }, +]; + +async function setTestEntries(storageType) { + for (const origin of TEST_ORIGINS) { + setTestEntry({ storageType, ...origin }); + } + + // Ensure we have the correct storage test state. + for (const origin of TEST_ORIGINS) { + await testEntryExists({ storageType, ...origin }); + } +} + +/** + * Run the base domain test with either localStorage or indexedDB. + * @param {('localStorage'|'indexedDB')} storageType + */ +async function runTestBaseDomain(storageType) { + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + await setTestEntries(storageType); + + // Clear entries of example.net including partitions. + await new Promise(aResolve => { + Services.clearData.deleteDataFromBaseDomain( + "example.net", + false, + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + await testEntryExists({ storageType, host: "example.net", expected: false }); + await testEntryExists({ + storageType, + host: "test.example.net", + expected: false, + }); + await testEntryExists({ storageType, host: "example.org" }); + + await testEntryExists({ + storageType, + host: "example.com", + topLevelBaseDomain: "example.net", + expected: false, + }); + await testEntryExists({ + storageType, + host: "example.com", + topLevelBaseDomain: "example.net", + originAttributes: { userContextId: 1 }, + expected: false, + }); + await testEntryExists({ + storageType, + host: "example.net", + topLevelBaseDomain: "example.org", + expected: false, + }); + await testEntryExists({ + storageType, + host: "test.example.net", + topLevelBaseDomain: "example.org", + expected: false, + }); + + // Cleanup + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); +} + +/** + * Run the host test with either localStorage or indexedDB. + * @param {('localStorage'|'indexedDB')} storageType + */ +async function runTestHost(storageType) { + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + await setTestEntries(storageType); + + // Clear entries of example.net without partitions. + await new Promise(aResolve => { + Services.clearData.deleteDataFromHost( + "example.net", + false, + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + await testEntryExists({ storageType, host: "example.net", expected: false }); + // QuotaCleaner#deleteByHost also clears subdomains. + await testEntryExists({ + storageType, + host: "test.example.net", + expected: false, + }); + await testEntryExists({ storageType, host: "example.org" }); + + await testEntryExists({ + storageType, + host: "example.com", + topLevelBaseDomain: "example.net", + expected: true, + }); + await testEntryExists({ + storageType, + host: "example.com", + topLevelBaseDomain: "example.net", + originAttributes: { userContextId: 1 }, + expected: true, + }); + // QuotaCleaner#deleteByHost ignores partitionKey. + await testEntryExists({ + storageType, + host: "example.net", + topLevelBaseDomain: "example.org", + expected: false, + }); + await testEntryExists({ + storageType, + host: "test.example.net", + topLevelBaseDomain: "example.org", + expected: false, + }); + + // Cleanup + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); +} + +/** + * Run the principal test with either localStorage or indexedDB. + * @param {('localStorage'|'indexedDB')} storageType + */ +async function runTestPrincipal(storageType) { + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + // First party + setTestEntry({ storageType, host: "example.net" }); + setTestEntry({ + storageType, + host: "example.net", + originAttributes: { userContextId: 2 }, + }); + setTestEntry({ + storageType, + host: "example.net", + originAttributes: { privateBrowsingId: 1 }, + }); + setTestEntry({ storageType, host: "test.example.net" }); + setTestEntry({ storageType, host: "example.org" }); + + // Third-party partitioned. + setTestEntry({ + storageType, + host: "example.net", + topLevelBaseDomain: "example.com", + }); + + // Ensure we have the correct storage test state. + await testEntryExists({ storageType, host: "example.net" }); + await testEntryExists({ + storageType, + host: "example.net", + originAttributes: { userContextId: 2 }, + }); + await testEntryExists({ + storageType, + host: "example.net", + originAttributes: { privateBrowsingId: 1 }, + }); + await testEntryExists({ storageType, host: "test.example.net" }); + await testEntryExists({ storageType, host: "example.org" }); + await testEntryExists({ + storageType, + host: "example.net", + topLevelBaseDomain: "example.com", + }); + + // Clear entries from principal with custom OA. + await new Promise(aResolve => { + Services.clearData.deleteDataFromPrincipal( + getPrincipal("example.net", null, { userContextId: 2 }), + false, + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + // Test that we only deleted entries for the exact origin. + await testEntryExists({ storageType, host: "example.net" }); + await testEntryExists({ + expected: false, + storageType, + host: "example.net", + originAttributes: { userContextId: 2 }, + }); + await testEntryExists({ + storageType, + host: "example.net", + originAttributes: { privateBrowsingId: 1 }, + }); + await testEntryExists({ storageType, host: "test.example.net" }); + await testEntryExists({ storageType, host: "example.org" }); + await testEntryExists({ + storageType, + host: "example.net", + topLevelBaseDomain: "example.com", + }); + + // Clear entries of from partitioned principal. + await new Promise(aResolve => { + Services.clearData.deleteDataFromPrincipal( + getPrincipal("example.net", "example.com"), + false, + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + // Test that we only deleted entries for the partition. + await testEntryExists({ storageType, host: "example.net" }); + await testEntryExists({ + expected: false, + storageType, + host: "example.net", + originAttributes: { userContextId: 2 }, + }); + await testEntryExists({ + storageType, + host: "example.net", + originAttributes: { privateBrowsingId: 1 }, + }); + await testEntryExists({ storageType, host: "test.example.net" }); + await testEntryExists({ storageType, host: "example.org" }); + await testEntryExists({ + expected: false, + storageType, + host: "example.net", + topLevelBaseDomain: "example.com", + }); + + // Clear entries of from principal without suffix. + await new Promise(aResolve => { + Services.clearData.deleteDataFromPrincipal( + getPrincipal("example.net", null), + false, + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + // Test that we only deleted entries for the given principal, and not entries + // for principals with the same host, but different OriginAttributes or + // subdomains. + await testEntryExists({ expected: false, storageType, host: "example.net" }); + await testEntryExists({ + expected: false, + storageType, + host: "example.net", + originAttributes: { userContextId: 2 }, + }); + await testEntryExists({ + storageType, + host: "example.net", + originAttributes: { privateBrowsingId: 1 }, + }); + + await testEntryExists({ storageType, host: "test.example.net" }); + await testEntryExists({ storageType, host: "example.org" }); + await testEntryExists({ + expected: false, + storageType, + host: "example.net", + topLevelBaseDomain: "example.com", + }); + + // Cleanup + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); +} + +// Tests + +add_task(function setup() { + // Allow setting local storage in xpcshell tests. + Services.prefs.setBoolPref("dom.storage.client_validation", false); +}); + +/** + * Tests deleting localStorage entries by host. + */ +add_task(async function test_host_localStorage() { + await runTestHost("localStorage"); +}); + +/** + * Tests deleting indexedDB entries by host. + */ +add_task(async function test_host_indexedDB() { + await runTestHost("indexedDB"); +}); + +/** + * Tests deleting (partitioned) localStorage entries by base domain. + */ +add_task(async function test_baseDomain_localStorage() { + await runTestBaseDomain("localStorage"); +}); + +/** + * Tests deleting (partitioned) indexedDB entries by base domain. + */ +add_task(async function test_baseDomain_indexedDB() { + await runTestBaseDomain("indexedDB"); +}); + +/** + * Tests deleting localStorage entries by principal. + */ +add_task(async function test_principal_localStorage() { + // Bug 1688221, Bug 1688665. + if (skipLocalStorageTests) { + info("Skipping test"); + return; + } + await runTestPrincipal("localStorage"); +}); + +function getRelativeFile(...components) { + const profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile); + + const file = profileDir.clone(); + for (const component of components) { + file.append(component); + } + + return file; +} + +function countSubitems(file) { + const entriesIterator = file.directoryEntries; + let count = 0; + while (entriesIterator.hasMoreElements()) { + ++count; + entriesIterator.nextFile; + } + return count; +} + +add_task(async function test_deleteAllAtShutdown() { + const storageType = "indexedDB"; + + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + const toBeRemovedDir = getRelativeFile("storage", "to-be-removed"); + if (toBeRemovedDir.exists()) { + toBeRemovedDir.remove(true); + } + + await setTestEntries(storageType); + + Services.startup.advanceShutdownPhase( + Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNTEARDOWN + ); + + // Clear entries from principal with custom OA. + for (const origin of TEST_ORIGINS) { + await new Promise(aResolve => { + Services.clearData.deleteDataFromPrincipal( + getPrincipal( + origin.host, + origin.topLevelBaseDomain, + origin.originAttributes + ), + false, + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + await testEntryExists({ expected: false, storageType, ...origin }); + } + + Assert.ok( + toBeRemovedDir.exists(), + "to-be-removed directory should exist now" + ); + + Assert.equal( + countSubitems(toBeRemovedDir), + TEST_ORIGINS.length, + `storage/to-be-removed has ${TEST_ORIGINS.length} subdirectories` + ); +}); diff --git a/toolkit/components/cleardata/tests/unit/test_security_settings.js b/toolkit/components/cleardata/tests/unit/test_security_settings.js new file mode 100644 index 0000000000..b14f567bab --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_security_settings.js @@ -0,0 +1,279 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test for SecuritySettingsCleaner. + * This tests both, the SiteSecurityService and the ClientAuthRememberService. + */ + +"use strict"; + +let gSSService = Cc["@mozilla.org/ssservice;1"].getService( + Ci.nsISiteSecurityService +); + +let cars = Cc["@mozilla.org/security/clientAuthRememberService;1"].getService( + Ci.nsIClientAuthRememberService +); + +let certDB = Cc["@mozilla.org/security/x509certdb;1"].getService( + Ci.nsIX509CertDB +); + +// These are not actual server and client certs. The ClientAuthRememberService +// does not care which certs we store decisions for, as long as they're valid. +let [clientCert] = certDB.getCerts(); + +function addSecurityInfo({ host, topLevelBaseDomain, originAttributes = {} }) { + let attrs = getOAWithPartitionKey({ topLevelBaseDomain }, originAttributes); + + let uri = Services.io.newURI(`https://${host}`); + + gSSService.processHeader(uri, "max-age=1000;", attrs); + + cars.rememberDecisionScriptable(host, attrs, clientCert); +} + +function addTestSecurityInfo() { + // First party + addSecurityInfo({ host: "example.net" }); + addSecurityInfo({ host: "test.example.net" }); + addSecurityInfo({ host: "example.org" }); + + // Third-party partitioned + addSecurityInfo({ host: "example.com", topLevelBaseDomain: "example.net" }); + addSecurityInfo({ host: "example.net", topLevelBaseDomain: "example.org" }); + addSecurityInfo({ + host: "test.example.net", + topLevelBaseDomain: "example.org", + }); + + // Ensure we have the correct state initially. + testSecurityInfo({ host: "example.net" }); + testSecurityInfo({ host: "test.example.net" }); + testSecurityInfo({ host: "example.org" }); + testSecurityInfo({ host: "example.com", topLevelBaseDomain: "example.net" }); + testSecurityInfo({ host: "example.net", topLevelBaseDomain: "example.org" }); + testSecurityInfo({ + host: "test.example.net", + topLevelBaseDomain: "example.org", + }); +} + +function testSecurityInfo({ + host, + topLevelBaseDomain, + expectedHSTS = true, + expectedCARS = true, + originAttributes = {}, +}) { + let attrs = getOAWithPartitionKey({ topLevelBaseDomain }, originAttributes); + + let messageSuffix = `for ${host}`; + if (topLevelBaseDomain) { + messageSuffix += ` partitioned under ${topLevelBaseDomain}`; + } + + let uri = Services.io.newURI(`https://${host}`); + let isSecure = gSSService.isSecureURI(uri, attrs); + Assert.equal( + isSecure, + expectedHSTS, + `HSTS ${expectedHSTS ? "is set" : "is not set"} ${messageSuffix}` + ); + + let hasRemembered = cars.hasRememberedDecisionScriptable(host, attrs, {}); + // CARS deleteDecisionsByHost does not include subdomains. That means for some + // test cases we expect a different remembered state. + Assert.equal( + hasRemembered, + expectedCARS, + `CAR ${expectedCARS ? "is set" : "is not set"} ${messageSuffix}` + ); +} + +add_task(async function test_baseDomain() { + gSSService.clearAll(); + + // ---- hsts cleaner ---- + addTestSecurityInfo(); + + // Clear hsts data of example.net including partitions. + await new Promise(aResolve => { + Services.clearData.deleteDataFromBaseDomain( + "example.net", + false, + Ci.nsIClearDataService.CLEAR_HSTS, + aResolve + ); + }); + + testSecurityInfo({ + host: "example.net", + expectedHSTS: false, + expectedCARS: true, + }); + // HSTSCleaner also removes subdomain settings. + testSecurityInfo({ + host: "test.example.net", + expectedHSTS: false, + expectedCARS: true, + }); + testSecurityInfo({ host: "example.org" }); + + testSecurityInfo({ + host: "example.com", + topLevelBaseDomain: "example.net", + expectedHSTS: false, + expectedCARS: true, + }); + testSecurityInfo({ + host: "example.net", + topLevelBaseDomain: "example.org", + expectedHSTS: false, + expectedCARS: true, + }); + testSecurityInfo({ + host: "test.example.net", + topLevelBaseDomain: "example.org", + expectedHSTS: false, + expectedCARS: true, + }); + + // ---- client auth remember cleaner ----- + addTestSecurityInfo(); + + // Clear security settings of example.net including partitions. + await new Promise(aResolve => { + Services.clearData.deleteDataFromBaseDomain( + "example.net", + false, + Ci.nsIClearDataService.CLEAR_CLIENT_AUTH_REMEMBER_SERVICE, + aResolve + ); + }); + + testSecurityInfo({ + host: "example.net", + expectedHSTS: true, + expectedCARS: false, + }); + // ClientAuthRememberCleaner also removes subdomain settings. + testSecurityInfo({ + host: "test.example.net", + expectedHSTS: true, + expectedCARS: false, + }); + testSecurityInfo({ host: "example.org" }); + + testSecurityInfo({ + host: "example.com", + topLevelBaseDomain: "example.net", + expectedHSTS: true, + expectedCARS: false, + }); + testSecurityInfo({ + host: "example.net", + topLevelBaseDomain: "example.org", + expectedHSTS: true, + expectedCARS: false, + }); + testSecurityInfo({ + host: "test.example.net", + topLevelBaseDomain: "example.org", + expectedHSTS: true, + expectedCARS: false, + }); + + // Cleanup + gSSService.clearAll(); +}); + +add_task(async function test_host() { + gSSService.clearAll(); + + // ---- HSTS cleaer ---- + addTestSecurityInfo(); + + // Clear security settings of example.net without partitions. + await new Promise(aResolve => { + Services.clearData.deleteDataFromHost( + "example.net", + false, + Ci.nsIClearDataService.CLEAR_HSTS, + aResolve + ); + }); + + testSecurityInfo({ + host: "example.net", + expectedHSTS: false, + expectedCARS: true, + }); + testSecurityInfo({ + host: "test.example.net", + expectedHSTS: false, + expectedCARS: true, + }); + testSecurityInfo({ host: "example.org" }); + + testSecurityInfo({ host: "example.com", topLevelBaseDomain: "example.net" }); + testSecurityInfo({ + host: "example.net", + topLevelBaseDomain: "example.org", + expectedHSTS: false, + expectedCARS: true, + }); + testSecurityInfo({ + host: "test.example.net", + topLevelBaseDomain: "example.org", + expectedHSTS: false, + expectedCARS: true, + }); + + // Cleanup + gSSService.clearAll(); + + // --- clientAuthRemember cleaner --- + + addTestSecurityInfo(); + + // Clear security settings of example.net without partitions. + await new Promise(aResolve => { + Services.clearData.deleteDataFromHost( + "example.net", + false, + Ci.nsIClearDataService.CLEAR_CLIENT_AUTH_REMEMBER_SERVICE, + aResolve + ); + }); + + testSecurityInfo({ + host: "example.net", + expectedHSTS: true, + expectedCARS: false, + }); + testSecurityInfo({ + host: "test.example.net", + expectedHSTS: true, + expectedCARS: true, + }); + testSecurityInfo({ host: "example.org" }); + + testSecurityInfo({ host: "example.com", topLevelBaseDomain: "example.net" }); + testSecurityInfo({ + host: "example.net", + topLevelBaseDomain: "example.org", + expectedHSTS: true, + expectedCARS: false, + }); + testSecurityInfo({ + host: "test.example.net", + topLevelBaseDomain: "example.org", + expectedHSTS: true, + expectedCARS: true, + }); + + // Cleanup + gSSService.clearAll(); +}); diff --git a/toolkit/components/cleardata/tests/unit/test_storage_permission.js b/toolkit/components/cleardata/tests/unit/test_storage_permission.js new file mode 100644 index 0000000000..a44e9f2c6a --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_storage_permission.js @@ -0,0 +1,398 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests for permissions + */ + +"use strict"; + +// Test that only the storageAccessAPI gets removed. +add_task(async function test_removing_storage_permission() { + const uri = Services.io.newURI("https://example.net"); + const principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + + Services.perms.addFromPrincipal( + principal, + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + Services.perms.addFromPrincipal( + principal, + "cookie", + Services.perms.ALLOW_ACTION + ); + + Assert.equal( + Services.perms.testExactPermissionFromPrincipal( + principal, + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION, + "There is a storageAccessAPI permission set" + ); + + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_STORAGE_ACCESS, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.equal( + Services.perms.testExactPermissionFromPrincipal( + principal, + "storageAccessAPI" + ), + Services.perms.UNKNOWN_ACTION, + "the storageAccessAPI permission has been removed" + ); + Assert.equal( + Services.perms.testExactPermissionFromPrincipal(principal, "cookie"), + Services.perms.ALLOW_ACTION, + "the cookie permission has not been removed" + ); + + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_PERMISSIONS, + value => aResolve() + ); + }); +}); + +// Test that the storageAccessAPI gets removed from a particular principal +add_task(async function test_removing_storage_permission_from_principal() { + const uri = Services.io.newURI("https://example.net"); + const principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + + const anotherUri = Services.io.newURI("https://example.com"); + const anotherPrincipal = + Services.scriptSecurityManager.createContentPrincipal(anotherUri, {}); + + Services.perms.addFromPrincipal( + principal, + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + Services.perms.addFromPrincipal( + anotherPrincipal, + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + Assert.equal( + Services.perms.testExactPermissionFromPrincipal( + principal, + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION, + "storageAccessAPI permission has been added to the first principal" + ); + Assert.equal( + Services.perms.testExactPermissionFromPrincipal( + anotherPrincipal, + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION, + "storageAccessAPI permission has been added to the second principal" + ); + + await new Promise(aResolve => { + Services.clearData.deleteDataFromPrincipal( + principal, + true /* user request */, + Ci.nsIClearDataService.CLEAR_STORAGE_ACCESS, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.equal( + Services.perms.testExactPermissionFromPrincipal( + principal, + "storageAccessAPI" + ), + Services.perms.UNKNOWN_ACTION, + "storageAccessAPI permission has been removed from the first principal" + ); + Assert.equal( + Services.perms.testExactPermissionFromPrincipal( + anotherPrincipal, + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION, + "storageAccessAPI permission has not been removed from the second principal" + ); + + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_PERMISSIONS, + value => aResolve() + ); + }); +}); + +// Test that the storageAccessAPI gets removed from a base domain. +add_task(async function test_removing_storage_permission_from_base_domainl() { + const uri = Services.io.newURI("https://example.net"); + const principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + const uriSub = Services.io.newURI("http://test.example.net"); + const principalSub = Services.scriptSecurityManager.createContentPrincipal( + uriSub, + {} + ); + + const anotherUri = Services.io.newURI("https://example.com"); + const anotherPrincipal = + Services.scriptSecurityManager.createContentPrincipal(anotherUri, {}); + + Services.perms.addFromPrincipal( + principal, + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + Services.perms.addFromPrincipal( + principalSub, + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + Services.perms.addFromPrincipal( + anotherPrincipal, + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + Assert.equal( + Services.perms.testExactPermissionFromPrincipal( + principal, + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION, + "storageAccessAPI permission has been added to the first principal" + ); + Assert.equal( + Services.perms.testExactPermissionFromPrincipal( + principalSub, + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION, + "storageAccessAPI permission has been added to the subdomain principal" + ); + Assert.equal( + Services.perms.testExactPermissionFromPrincipal( + anotherPrincipal, + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION, + "storageAccessAPI permission has been added to the second principal" + ); + + await new Promise(aResolve => { + Services.clearData.deleteDataFromBaseDomain( + "example.net", + true /* user request */, + Ci.nsIClearDataService.CLEAR_STORAGE_ACCESS, + value => { + Assert.equal(value, 0); + aResolve(); + } + ); + }); + + Assert.equal( + Services.perms.testExactPermissionFromPrincipal( + principal, + "storageAccessAPI" + ), + Services.perms.UNKNOWN_ACTION, + "storageAccessAPI permission has been removed from the first principal" + ); + Assert.equal( + Services.perms.testExactPermissionFromPrincipal( + principalSub, + "storageAccessAPI" + ), + Services.perms.UNKNOWN_ACTION, + "storageAccessAPI permission has been removed from the sub domain principal" + ); + Assert.equal( + Services.perms.testExactPermissionFromPrincipal( + anotherPrincipal, + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION, + "storageAccessAPI permission has not been removed from the second principal" + ); + + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_PERMISSIONS, + value => aResolve() + ); + }); +}); + +// Tests the deleteUserInteractionForClearingHistory function. +add_task(async function test_deleteUserInteractionForClearingHistory() { + // These should be retained. + PermissionTestUtils.add( + "https://example.com", + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + PermissionTestUtils.add( + "https://sub.example.com", + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + PermissionTestUtils.add( + "https://sub.example.com^userContextId=3", + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + + // These should be removed. + PermissionTestUtils.add( + "https://example.org", + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + PermissionTestUtils.add( + "https://sub.example.org", + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + PermissionTestUtils.add( + "https://sub.example.org^userContextId=3", + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + + let principalWithStorage = + Services.scriptSecurityManager.createContentPrincipalFromOrigin( + "https://sub.example.com" + ); + + await new Promise(resolve => { + return Services.clearData.deleteUserInteractionForClearingHistory( + [principalWithStorage], + 0, + resolve + ); + }); + + Assert.equal( + PermissionTestUtils.testExactPermission( + "https://example.org", + "storageAccessAPI" + ), + Services.perms.UNKNOWN_ACTION + ); + Assert.equal( + PermissionTestUtils.testExactPermission( + "https://sub.example.org", + "storageAccessAPI" + ), + Services.perms.UNKNOWN_ACTION + ); + Assert.equal( + PermissionTestUtils.testExactPermission( + "https://sub.example.org^userContextId=3", + "storageAccessAPI" + ), + Services.perms.UNKNOWN_ACTION + ); + + Assert.equal( + PermissionTestUtils.testExactPermission( + "https://example.com", + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION + ); + Assert.equal( + PermissionTestUtils.testExactPermission( + "https://sub.example.com", + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION + ); + Assert.equal( + PermissionTestUtils.testExactPermission( + "https://sub.example.com^userContextId=3", + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION + ); + + // This permission is set earlier than the timestamp and should be retained. + PermissionTestUtils.add( + "https://example.net", + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + + // Add some time in between taking the snapshot of the timestamp + // to avoid flakyness. + await new Promise(c => do_timeout(100, c)); + let timestamp = Date.now(); + await new Promise(c => do_timeout(100, c)); + + // This permission is set later than the timestamp and should be removed. + PermissionTestUtils.add( + "https://example.org", + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + + await new Promise(resolve => { + return Services.clearData.deleteUserInteractionForClearingHistory( + [principalWithStorage], + // ClearDataService takes PRTime (microseconds) + timestamp * 1000, + resolve + ); + }); + + Assert.equal( + PermissionTestUtils.testExactPermission( + "https://example.org", + "storageAccessAPI" + ), + Services.perms.UNKNOWN_ACTION + ); + Assert.equal( + PermissionTestUtils.testExactPermission( + "https://example.net", + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION + ); + Assert.equal( + PermissionTestUtils.testExactPermission( + "https://example.com", + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION + ); + + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_PERMISSIONS, + value => aResolve() + ); + }); +}); diff --git a/toolkit/components/cleardata/tests/unit/xpcshell.toml b/toolkit/components/cleardata/tests/unit/xpcshell.toml new file mode 100644 index 0000000000..2df07abcea --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/xpcshell.toml @@ -0,0 +1,40 @@ +[DEFAULT] +tags = "condprof" +firefox-appdir = "browser" +head = "head.js" +skip-if = ["os == 'android'"] +support-files = "" +prefs = [ + "privacy.bounceTrackingProtection.enabled=true", + "privacy.bounceTrackingProtection.enableTestMode=true", + "privacy.bounceTrackingProtection.bounceTrackingPurgeTimerPeriodSec=0", +] + +["test_basic.js"] + +["test_bounce_tracking_protection.js"] + +["test_certs.js"] + +["test_cookie_banner_handling.js"] + +["test_cookies.js"] + +["test_downloads.js"] + +["test_fingerprinting_protection_state.js"] + +["test_identity_credential_storage.js"] + +["test_network_cache.js"] +skip-if = ["condprof"] # Bug 1769154 - expected fail w/condprof + +["test_passwords.js"] + +["test_permissions.js"] + +["test_quota.js"] + +["test_security_settings.js"] + +["test_storage_permission.js"] |