Bug 1174900 - Capture doorhanger password field toggle should stay hidden for master password users;r=MattN draft
authorgasolin <gasolin@gmail.com>
Mon, 06 Jun 2016 14:07:50 +0800
changeset 375570 8b2443f6cf9c765ecc004c613e755f2eb481fe68
parent 375564 925a96e53262fabe127d2a6405f98438d18cff6b
child 522902 25dd786b22e2bc8e9c908aa95588eb8056df7a1e
push id20312
push userbmo:gasolin@mozilla.com
push dateMon, 06 Jun 2016 06:53:38 +0000
reviewersMattN
bugs1174900
milestone49.0a1
Bug 1174900 - Capture doorhanger password field toggle should stay hidden for master password users;r=MattN MozReview-Commit-ID: DLlu2WhQamN
browser/components/preferences/in-content/security.js
toolkit/components/passwordmgr/LoginHelper.jsm
toolkit/components/passwordmgr/nsLoginManagerPrompter.js
toolkit/components/passwordmgr/test/LoginTestUtils.jsm
toolkit/components/passwordmgr/test/browser/browser_notifications_2.js
--- a/browser/components/preferences/in-content/security.js
+++ b/browser/components/preferences/in-content/security.js
@@ -1,12 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+
 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 var gSecurityPane = {
   _pane: null,
 
   /**
    * Initializes master password UI.
    */
@@ -131,17 +134,17 @@ var gSecurityPane = {
   /**
    * Initializes master password UI: the "use master password" checkbox, selects
    * the master password button to show, and enables/disables it as necessary.
    * The master password is controlled by various bits of NSS functionality, so
    * the UI for it can't be controlled by the normal preference bindings.
    */
   _initMasterPasswordUI: function ()
   {
-    var noMP = !this._masterPasswordSet();
+    var noMP = !LoginHelper.isMasterPasswordSet();
 
     var button = document.getElementById("changeMasterPassword");
     button.disabled = noMP;
 
     var checkbox = document.getElementById("useMasterPassword");
     checkbox.checked = !noMP;
   },
 
@@ -214,35 +217,16 @@ var gSecurityPane = {
     if (!blockDownloadsPref.value) {
       blockUncommonUnwanted.setAttribute("disabled", "true");
     }
 
     blockUncommonUnwanted.checked = blockUnwantedPref.value && blockUncommonPref.value;
   },
 
   /**
-   * Returns true if the user has a master password set and false otherwise.
-   */
-  _masterPasswordSet: function ()
-  {
-    var secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].
-                   getService(Ci.nsIPKCS11ModuleDB);
-    var slot = secmodDB.findSlotByName("");
-    if (slot) {
-      var status = slot.status;
-      var hasMP = status != Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED &&
-                  status != Ci.nsIPKCS11Slot.SLOT_READY;
-      return hasMP;
-    } else {
-      // XXX I have no bloody idea what this means
-      return false;
-    }
-  },
-
-  /**
    * Enables/disables the master password button depending on the state of the
    * "use master password" checkbox, and prompts for master password removal if
    * one is set.
    */
   updateMasterPasswordButton: function ()
   {
     var checkbox = document.getElementById("useMasterPassword");
     var button = document.getElementById("changeMasterPassword");
--- a/toolkit/components/passwordmgr/LoginHelper.jsm
+++ b/toolkit/components/passwordmgr/LoginHelper.jsm
@@ -640,15 +640,32 @@ this.LoginHelper = {
         toDeletes.add(Path.join(profileDir, signonFile));
         Services.prefs.clearUserPref(pref);
       } catch (e) {}
     }
 
     for (let file of toDeletes) {
       File.remove(file);
     }
+  },
+
+  /**
+   * Returns true if the user has a master password set and false otherwise.
+   */
+  isMasterPasswordSet() {
+    let secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].
+                   getService(Ci.nsIPKCS11ModuleDB);
+    let slot = secmodDB.findSlotByName("");
+    if (slot) {
+      let status = slot.status;
+      let hasMP = status != Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED &&
+                  status != Ci.nsIPKCS11Slot.SLOT_READY;
+      return hasMP;
+    } else {
+      return false;
+    }
   }
 };
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let logger = LoginHelper.createLogger("LoginHelper");
   return logger;
 });
--- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
+++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
@@ -962,25 +962,24 @@ LoginManagerPrompter.prototype = {
         eventCallback: function (topic) {
           switch (topic) {
             case "showing":
               currentNotification = this;
               chromeDoc.getElementById("password-notification-username")
                        .addEventListener("input", onInput);
               chromeDoc.getElementById("password-notification-password")
                        .addEventListener("input", onInput);
+              let toggleBtn = chromeDoc.getElementById("password-notification-visibilityToggle");
+
               if (Services.prefs.getBoolPref("signon.rememberSignons.visibilityToggle")) {
-                chromeDoc.getElementById("password-notification-visibilityToggle")
-                         .addEventListener("command", onVisibilityToggle);
-                chromeDoc.getElementById("password-notification-visibilityToggle")
-                         .setAttribute("label", togglePasswordLabel);
-                chromeDoc.getElementById("password-notification-visibilityToggle")
-                         .setAttribute("accesskey", togglePasswordAccessKey);
-                chromeDoc.getElementById("password-notification-visibilityToggle")
-                         .removeAttribute("hidden");
+                toggleBtn.addEventListener("command", onVisibilityToggle);
+                toggleBtn.setAttribute("label", togglePasswordLabel);
+                toggleBtn.setAttribute("accesskey", togglePasswordAccessKey);
+                toggleBtn.setAttribute("hidden",
+                  LoginHelper.isMasterPasswordSet());
               }
               break;
             case "shown":
               writeDataToUI();
               break;
             case "dismissed":
               readDataFromUI();
               // Fall through.
--- a/toolkit/components/passwordmgr/test/LoginTestUtils.jsm
+++ b/toolkit/components/passwordmgr/test/LoginTestUtils.jsm
@@ -244,8 +244,59 @@ this.LoginTestUtils.recipes = {
     if (!LoginManagerParent.recipeParentPromise) {
       return null;
     }
     return LoginManagerParent.recipeParentPromise.then((recipeParent) => {
       return recipeParent;
     });
   },
 };
+
+this.LoginTestUtils.master = {
+  masterPassword: "omgsecret!",
+
+  enableMasterPassword() {
+    this._setMasterPassword(true);
+  },
+
+  disableMasterPassword() {
+    this._setMasterPassword(false);
+  },
+
+  _setMasterPassword(enable) {
+    let oldPW, newPW;
+    if (enable) {
+      oldPW = "";
+      newPW = this.masterPassword;
+    } else {
+      oldPW = this.masterPassword;
+      newPW = "";
+    }
+    // Set master password. Note that this does not log you in, so the next
+    // invocation of pwmgr can trigger a MP prompt.
+
+    try {
+      let status;
+      let secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"]
+                          .getService(Ci.nsIPKCS11ModuleDB);
+      let slot = secmodDB.findSlotByName("");
+      if (slot) {
+        status = slot.status;
+      } else {
+        return false;
+      }
+
+      let pk11db = Cc["@mozilla.org/security/pk11tokendb;1"]
+                     .getService(Ci.nsIPK11TokenDB);
+      let token = pk11db.findTokenByName("");
+      if (status == Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED) {
+        token.initPassword(newPW);
+      } else if (status == Ci.nsIPKCS11Slot.SLOT_READY) {
+        info("MP change from " + oldPW + " to " + newPW);
+        token.changePassword(oldPW, newPW);
+      }
+      return true;
+    } catch(e) {
+      dump("MasterPassword.setPassword: " + e);
+    }
+    return false;
+  }
+}
--- a/toolkit/components/passwordmgr/test/browser/browser_notifications_2.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_notifications_2.js
@@ -80,8 +80,45 @@ add_task(function* test_toggle_password(
       Assert.ok(toggleCheckbox.checked);
       Assert.equal(passwordTextbox.type, "", "Password textbox changed to plain text");
 
       yield EventUtils.synthesizeMouseAtCenter(toggleCheckbox, {});
       Assert.ok(!toggleCheckbox.checked);
       Assert.equal(passwordTextbox.type, "password", "Password textbox changed to * text");
     });
 });
+
+/**
+ * Test that the doorhanger password toggle checkbox is disabled
+ * when the master word is set.
+ *
+ */
+add_task(function* test_checkbox_disabled_if_has_master_password() {
+  yield BrowserTestUtils.withNewTab({
+      gBrowser,
+      url: "https://example.com/browser/toolkit/components/" +
+           "passwordmgr/test/browser/form_basic.html",
+    }, function* (browser) {
+      // Submit the form in the content page with the credentials from the test
+      // case. This will cause the doorhanger notification to be displayed.
+      let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+                                                       "popupshown");
+      LoginTestUtils.master.enableMasterPassword();
+
+      yield ContentTask.spawn(browser, null,
+        function* () {
+          let doc = content.document;
+          doc.getElementById("form-basic-username").value = "username";
+          doc.getElementById("form-basic-password").value = "p";
+          doc.getElementById("form-basic").submit();
+        });
+      yield promiseShown;
+
+      let notificationElement = PopupNotifications.panel.childNodes[0];
+      let passwordTextbox = notificationElement.querySelector("#password-notification-password");
+      let toggleCheckbox = notificationElement.querySelector("#password-notification-visibilityToggle");
+
+      Assert.equal(passwordTextbox.type, "password", "Password textbox should show * text");
+      Assert.ok(toggleCheckbox.getAttribute("hidden"), "checkbox is hidden when master password is set");
+
+      LoginTestUtils.master.disableMasterPassword();
+    });
+});