Bug 1422365 - Introduce nsIClearDataService - part 5 - passwords, r=johannh
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 01 Jun 2018 14:30:00 +0200
changeset 420839 7f2aed939175cff35ca48725282f23a365d24593
parent 420838 bb116c8d9d2cb9cdfdf8bf46b7b9cbcc931951bc
child 420840 9c6127c3f4a8393217b1b9fccb02daf984b7ddfb
push id103898
push useramarchesini@mozilla.com
push dateFri, 01 Jun 2018 12:31:55 +0000
treeherdermozilla-inbound@ee1e13b50338 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjohannh
bugs1422365
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1422365 - Introduce nsIClearDataService - part 5 - passwords, r=johannh
toolkit/components/cleardata/ClearDataService.js
toolkit/components/cleardata/nsIClearDataService.idl
toolkit/components/cleardata/tests/unit/test_passwords.js
toolkit/components/cleardata/tests/unit/xpcshell.ini
toolkit/forgetaboutsite/ForgetAboutSite.jsm
--- a/toolkit/components/cleardata/ClearDataService.js
+++ b/toolkit/components/cleardata/ClearDataService.js
@@ -193,32 +193,66 @@ const DownloadsCleaner = {
 
   deleteAll() {
     return Downloads.getList(Downloads.ALL).then(aList => {
       aList.removeFinished(null);
     });
   },
 };
 
+const PasswordsCleaner = {
+  deleteByHost(aHost, aOriginAttributes) {
+    return this._deleteInternal(aLogin => hasRootDomain(aLogin.hostname, aHost));
+  },
+
+  deleteAll() {
+    return this._deleteInternal(() => true);
+  },
+
+  _deleteInternal(aCb) {
+    return new Promise(aResolve => {
+      try {
+        let logins = Services.logins.getAllLogins();
+        for (let login of logins) {
+          if (aCb(login)) {
+            Services.logins.removeLogin(login);
+          }
+        }
+      } catch (ex) {
+        // XXXehsan: is there a better way to do this rather than this
+        // hacky comparison?
+        if (!ex.message.includes("User canceled Master Password entry")) {
+          throw new Error("Exception occured in clearing passwords :" + ex);
+        }
+      }
+
+      aResolve();
+    });
+  },
+};
+
 // Here the map of Flags-Cleaner.
 const FLAGS_MAP = [
  { flag: Ci.nsIClearDataService.CLEAR_COOKIES,
    cleaner: CookieCleaner },
 
  { flag: Ci.nsIClearDataService.CLEAR_NETWORK_CACHE,
    cleaner: NetworkCacheCleaner },
 
  { flag: Ci.nsIClearDataService.CLEAR_IMAGE_CACHE,
    cleaner: ImageCacheCleaner, },
 
  { flag: Ci.nsIClearDataService.CLEAR_PLUGIN_DATA,
    cleaner: PluginDataCleaner, },
 
  { flag: Ci.nsIClearDataService.CLEAR_DOWNLOADS,
    cleaner: DownloadsCleaner, },
+
+ { flag: Ci.nsIClearDataService.CLEAR_PASSWORDS,
+   cleaner: PasswordsCleaner, },
 ];
 
 this.ClearDataService = function() {};
 
 ClearDataService.prototype = Object.freeze({
   classID: Components.ID("{0c06583d-7dd8-4293-b1a5-912205f779aa}"),
   QueryInterface: ChromeUtils.generateQI([Ci.nsIClearDataService]),
   _xpcom_factory: XPCOMUtils.generateSingletonFactory(ClearDataService),
--- a/toolkit/components/cleardata/nsIClearDataService.idl
+++ b/toolkit/components/cleardata/nsIClearDataService.idl
@@ -103,19 +103,23 @@ interface nsIClearDataService : nsISuppo
    */
   const uint32_t CLEAR_PLUGIN_DATA = 1 << 3;
 
   /**
    * Completed downloads.
    */
   const uint32_t CLEAR_DOWNLOADS = 1 << 4;
 
+  /**
+   * Stored passwords.
+   */
+  const uint32_t CLEAR_PASSWORDS = 1 << 5;
+
   /* TODO
   const uint32_t CLEAR_EME = 1 << 4;
-  const uint32_t CLEAR_PASSWORDS = 1 << 6;
   const uint32_t CLEAR_PERMISSIONS = 1 << 7;
   const uint32_t CLEAR_DOM_QUOTA = 1 << 8;
   const uint32_t CLEAR_CONTENT_PREFERENCES = 1 << 9;
   const uint32_t CLEAR_PREDICTOR_CACHE = 1 << 10;
   const uint32_t CLEAR_DOM_PUSH_NOTIFICATIONS = 1 << 11;
   const uint32_t CLEAR_HSTS = 1 << 12;
   const uint32_t CLEAR_HPKP = 1 << 13;
   const uint32_t CLEAR_HISTORY = 1 << 14;
new file mode 100644
--- /dev/null
+++ b/toolkit/components/cleardata/tests/unit/test_passwords.js
@@ -0,0 +1,78 @@
+/**
+ * Tests for passwords.
+ */
+
+"use strict";
+
+const URL = "http://example.com";
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://testing-common/LoginTestUtils.jsm");
+
+add_task(async function test_principal_downloads() {
+  // Store the strings "user" and "pass" using similarly looking glyphs.
+  let loginInfo = LoginTestUtils.testData.formLogin({
+    hostname: URL,
+    formSubmitURL: URL,
+    username: "admin",
+    password: "12345678",
+    usernameField: "field_username",
+    passwordField: "field_password",
+  });
+  Services.logins.addLogin(loginInfo);
+
+  Assert.equal(countLogins(URL), 1);
+
+  let uri = Services.io.newURI(URL);
+  let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+
+  await new Promise(resolve => {
+    Services.clearData.deleteDataFromPrincipal(principal, true /* user request */,
+                                               Ci.nsIClearDataService.CLEAR_PASSWORDS, value => {
+      Assert.equal(value, 0);
+      resolve();
+    });
+  });
+
+  Assert.equal(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({
+    hostname: URL,
+    formSubmitURL: URL,
+    username: "admin",
+    password: "12345678",
+    usernameField: "field_username",
+    passwordField: "field_password",
+  });
+  Services.logins.addLogin(loginInfo);
+
+  Assert.equal(countLogins(URL), 1);
+
+  await new Promise(resolve => {
+    Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_PASSWORDS, value => {
+      Assert.equal(value, 0);
+      resolve();
+    });
+  });
+
+  Assert.equal(countLogins(URL), 0);
+
+  LoginTestUtils.clearData();
+});
+
+function countLogins(host) {
+  let count = 0;
+  const logins = Services.logins.getAllLogins();
+  for (const login of logins) {
+    if (login.hostname == host) {
+      ++count;
+    }
+  }
+
+  return count;
+}
--- a/toolkit/components/cleardata/tests/unit/xpcshell.ini
+++ b/toolkit/components/cleardata/tests/unit/xpcshell.ini
@@ -1,7 +1,8 @@
 [DEFAULT]
 head = head.js
 support-files =
 
 [test_basic.js]
 [test_cookies.js]
 [test_downloads.js]
+[test_passwords.js]
--- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm
+++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
@@ -58,31 +58,16 @@ var ForgetAboutSite = {
     promises.push((async function() {
       let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
                 getService(Ci.mozIGeckoMediaPluginChromeService);
       mps.forgetThisSite(aDomain, JSON.stringify({}));
     })().catch(ex => {
       throw new Error("Exception thrown while clearing Encrypted Media Extensions: " + ex);
     }));
 
-    // Passwords
-    promises.push((async function() {
-      // Clear all passwords for domain
-      let logins = Services.logins.getAllLogins();
-      for (let i = 0; i < logins.length; i++)
-        if (hasRootDomain(logins[i].hostname, aDomain))
-          Services.logins.removeLogin(logins[i]);
-    })().catch(ex => {
-      // XXXehsan: is there a better way to do this rather than this
-      // hacky comparison?
-      if (!ex.message.includes("User canceled Master Password entry")) {
-        throw new Error("Exception occured in clearing passwords :" + ex);
-      }
-    }));
-
     // Permissions
     // Enumerate all of the permissions, and if one matches, remove it
     let enumerator = Services.perms.enumerator;
     while (enumerator.hasMoreElements()) {
       let perm = enumerator.getNext().QueryInterface(Ci.nsIPermission);
       promises.push(new Promise((resolve, reject) => {
         try {
           if (hasRootDomain(perm.principal.URI.host, aDomain)) {