Bug 1393745 - [Form Autofill] Wrap up masterpassword decrypt API in FormAutofillHandler. r=lchang draft
authorsteveck-chung <schung@mozilla.com>
Fri, 25 Aug 2017 19:28:47 +0800
changeset 653857 5489d5346bf00101d20cffe2c692178c6f8d59a7
parent 653570 2afce84b0ab5131daee32bc27bb2cb89a570edc4
child 728441 d868c87a703e6a4cdfa57bbca5eb2130f7aa7541
push id76436
push userbmo:schung@mozilla.com
push dateMon, 28 Aug 2017 05:20:35 +0000
reviewerslchang
bugs1393745
milestone57.0a1
Bug 1393745 - [Form Autofill] Wrap up masterpassword decrypt API in FormAutofillHandler. r=lchang MozReview-Commit-ID: LZdwhmmJcFD
browser/extensions/formautofill/FormAutofillHandler.jsm
browser/extensions/formautofill/FormAutofillParent.jsm
browser/extensions/formautofill/test/unit/test_autofillFormFields.js
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -7,24 +7,23 @@
  */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["FormAutofillHandler"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
+Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillHeuristics",
                                   "resource://formautofill/FormAutofillHeuristics.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "MasterPassword",
-                                  "resource://formautofill/MasterPassword.jsm");
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
 /**
  * Handles profile autofill for a DOM Form element.
  * @param {FormLike} form Form that need to be auto filled
  */
@@ -279,25 +278,24 @@ FormAutofillHandler.prototype = {
     );
     let targetSet;
     if (FormAutofillUtils.isCreditCardField(focusedDetail.fieldName)) {
       // When Master Password is enabled by users, the decryption process
       // should prompt Master Password dialog to get the decrypted credit
       // card number. Otherwise, the number can be decrypted with the default
       // password.
       if (profile["cc-number-encrypted"]) {
-        try {
-          profile["cc-number"] = await MasterPassword.decrypt(profile["cc-number-encrypted"], true);
-        } catch (e) {
-          if (e.result == Cr.NS_ERROR_ABORT) {
-            log.warn("User canceled master password entry");
-            return;
-          }
-          throw e;
+        let decrypted = await this._decrypt(profile["cc-number-encrypted"], true);
+
+        if (!decrypted) {
+          // Early return if the decrypted is empty or undefined
+          return;
         }
+
+        profile["cc-number"] = decrypted;
       }
       targetSet = this.creditCard;
     } else if (FormAutofillUtils.isAddressField(focusedDetail.fieldName)) {
       targetSet = this.address;
     } else {
       throw new Error("Unknown form fields");
     }
 
@@ -567,9 +565,20 @@ FormAutofillHandler.prototype = {
 
     if (data.creditCard && !data.creditCard.record["cc-number"]) {
       log.debug("No credit card record saving since card number is empty");
       delete data.creditCard;
     }
 
     return data;
   },
+
+  async _decrypt(cipherText, reauth) {
+    return new Promise((resolve) => {
+      Services.cpmm.addMessageListener("FormAutofill:DecryptedString", function getResult(result) {
+        Services.cpmm.removeMessageListener("FormAutofill:DecryptedString", getResult);
+        resolve(result.data);
+      });
+
+      Services.cpmm.sendAsyncMessage("FormAutofill:GetDecryptedString", {cipherText, reauth});
+    });
+  },
 };
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -87,16 +87,17 @@ FormAutofillParent.prototype = {
     Services.obs.addObserver(this, "advanced-pane-loaded");
     Services.ppmm.addMessageListener("FormAutofill:InitStorage", this);
     Services.ppmm.addMessageListener("FormAutofill:GetRecords", this);
     Services.ppmm.addMessageListener("FormAutofill:SaveAddress", this);
     Services.ppmm.addMessageListener("FormAutofill:SaveCreditCard", this);
     Services.ppmm.addMessageListener("FormAutofill:RemoveAddresses", this);
     Services.ppmm.addMessageListener("FormAutofill:RemoveCreditCards", this);
     Services.ppmm.addMessageListener("FormAutofill:OpenPreferences", this);
+    Services.ppmm.addMessageListener("FormAutofill:GetDecryptedString", this);
     Services.mm.addMessageListener("FormAutofill:OnFormSubmit", this);
 
     // Observing the pref and storage changes
     Services.prefs.addObserver(ENABLED_AUTOFILL_ADDRESSES_PREF, this);
     Services.prefs.addObserver(ENABLED_AUTOFILL_CREDITCARDS_PREF, this);
     Services.obs.addObserver(this, "formautofill-storage-changed");
   },
 
@@ -218,16 +219,31 @@ FormAutofillParent.prototype = {
       }
       case "FormAutofill:OnFormSubmit": {
         this._onFormSubmit(data, target);
         break;
       }
       case "FormAutofill:OpenPreferences": {
         const win = RecentWindow.getMostRecentBrowserWindow();
         win.openPreferences("panePrivacy", {origin: "autofillFooter"});
+        break;
+      }
+      case "FormAutofill:GetDecryptedString": {
+        let {cipherText, reauth} = data;
+        let string;
+        try {
+          string = await MasterPassword.decrypt(cipherText, reauth);
+        } catch (e) {
+          if (e.result != Cr.NS_ERROR_ABORT) {
+            throw e;
+          }
+          log.warn("User canceled master password entry");
+        }
+        target.sendAsyncMessage("FormAutofill:DecryptedString", string);
+        break;
       }
     }
   },
 
   /**
    * Uninitializes FormAutofillParent. This is for testing only.
    *
    * @private
--- a/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
@@ -498,16 +498,29 @@ function do_test(testcases, testFn) {
         }
 
         let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
                                                   testcase.document);
         let form = doc.querySelector("form");
         let formLike = FormLikeFactory.createFromForm(form);
         let handler = new FormAutofillHandler(formLike);
         let promises = [];
+        // Replace the interal decrypt method with MasterPassword API
+        handler._decrypt = async (cipherText, reauth) => {
+          let string;
+          try {
+            string = await MasterPassword.decrypt(cipherText, reauth);
+          } catch (e) {
+            if (e.result != Cr.NS_ERROR_ABORT) {
+              throw e;
+            }
+            do_print("User canceled master password entry");
+          }
+          return string;
+        };
 
         handler.collectFormFields();
         let handlerInfo = handler[testcase.expectedFillingForm];
         handlerInfo.fieldDetails.forEach(field => {
           let element = field.elementWeakRef.get();
           if (!testcase.profileData[field.fieldName]) {
             // Avoid waiting for `change` event of a input with a blank value to
             // be filled.