Bug 1650645 - Import logins from KeepassXC by lower-casing column names. r=MattN
authorTom Schuster <evilpies@gmail.com>
Wed, 09 Dec 2020 05:23:11 +0000
changeset 559927 22c49c20fe8352702f5a5329bc14d4becb9d6e9d
parent 559926 1fcd9c94173733aa56a49f0dbeff890bd9557b62
child 559928 03dee0cdd71a176752b327c21da0dcd3da0a9663
push id38016
push usermalexandru@mozilla.com
push dateWed, 09 Dec 2020 21:35:04 +0000
treeherdermozilla-central@7765d979d044 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs1650645
milestone85.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 1650645 - Import logins from KeepassXC by lower-casing column names. r=MattN Differential Revision: https://phabricator.services.mozilla.com/D97917
toolkit/components/passwordmgr/LoginCSVImport.jsm
toolkit/components/passwordmgr/test/unit/test_module_LoginCSVImport.js
--- a/toolkit/components/passwordmgr/LoginCSVImport.jsm
+++ b/toolkit/components/passwordmgr/LoginCSVImport.jsm
@@ -25,26 +25,30 @@ XPCOMUtils.defineLazyGetter(this, "d3", 
   let d3Scope = Cu.Sandbox(null);
   Services.scriptloader.loadSubScript(
     "chrome://global/content/third_party/d3/d3.js",
     d3Scope
   );
   return Cu.waiveXrays(d3Scope.d3);
 });
 
+/**
+ * All the CSV column names will be converted to lower case before lookup
+ * so they must be specified here in lower case.
+ */
 const FIELD_TO_CSV_COLUMNS = {
   origin: ["url", "login_uri"],
   username: ["username", "login_username"],
   password: ["password", "login_password"],
-  httpRealm: ["httpRealm"],
-  formActionOrigin: ["formActionOrigin"],
+  httpRealm: ["httprealm"],
+  formActionOrigin: ["formactionorigin"],
   guid: ["guid"],
-  timeCreated: ["timeCreated"],
-  timeLastUsed: ["timeLastUsed"],
-  timePasswordChanged: ["timePasswordChanged"],
+  timeCreated: ["timecreated"],
+  timeLastUsed: ["timelastused"],
+  timePasswordChanged: ["timepasswordchanged"],
 };
 
 /**
  * Provides an object that has a method to import login-related data CSV files
  */
 class LoginCSVImport {
   static get MIGRATION_HISTOGRAM_KEY() {
     return "login_csv";
@@ -54,17 +58,17 @@ class LoginCSVImport {
    * Returns a map that has the csv column name as key and the value the field name.
    *
    * @returns {Map} A map that has the csv column name as key and the value the field name.
    */
   static _getCSVColumnToFieldMap() {
     let csvColumnToField = new Map();
     for (let [field, columns] of Object.entries(FIELD_TO_CSV_COLUMNS)) {
       for (let column of columns) {
-        csvColumnToField.set(column, field);
+        csvColumnToField.set(column.toLowerCase(), field);
       }
     }
     return csvColumnToField;
   }
 
   /**
    * Builds a vanilla JS object containing all the login fields from a row of CSV cells.
    *
@@ -72,17 +76,17 @@ class LoginCSVImport {
    *        An object created from a csv row. The keys are the csv column names, the values are the cells.
    * @param {Map} csvColumnToFieldMap
    *        A map where the keys are the csv properties and the values are the object keys.
    * @returns {object} Representing login object with only properties, not functions.
    */
   static _getVanillaLoginFromCSVObject(csvObject, csvColumnToFieldMap) {
     let vanillaLogin = Object.create(null);
     for (let columnName of Object.keys(csvObject)) {
-      let fieldName = csvColumnToFieldMap.get(columnName);
+      let fieldName = csvColumnToFieldMap.get(columnName.toLowerCase());
       if (!fieldName) {
         continue;
       }
 
       if (
         typeof vanillaLogin[fieldName] != "undefined" &&
         vanillaLogin[fieldName] !== csvObject[columnName]
       ) {
@@ -115,17 +119,19 @@ class LoginCSVImport {
       "FX_MIGRATION_LOGINS_IMPORT_MS",
       LoginCSVImport.MIGRATION_HISTOGRAM_KEY
     );
     let responsivenessMonitor = new ResponsivenessMonitor();
     let csvColumnToFieldMap = LoginCSVImport._getCSVColumnToFieldMap();
     let csvString = await OS.File.read(filePath, { encoding: "utf-8" });
     let parsedLines = d3.csv.parse(csvString);
     let fieldsInFile = new Set(
-      Object.keys(parsedLines[0] || {}).map(col => csvColumnToFieldMap.get(col))
+      Object.keys(parsedLines[0] || {}).map(col => {
+        return csvColumnToFieldMap.get(col.toLowerCase());
+      })
     );
     if (
       parsedLines[0] &&
       (!fieldsInFile.has("origin") ||
         !fieldsInFile.has("username") ||
         !fieldsInFile.has("password"))
     ) {
       // The username *value* can be empty but we require a username column to
--- a/toolkit/components/passwordmgr/test/unit/test_module_LoginCSVImport.js
+++ b/toolkit/components/passwordmgr/test/unit/test_module_LoginCSVImport.js
@@ -432,8 +432,41 @@ add_task(async function test_import_from
     ],
     "Check that a new Chrome login was added with the correct fields",
     (a, e) =>
       a.equals(e) &&
       checkMetaInfo(a, e, ["timesUsed"]) &&
       checkLoginNewlyCreated(a)
   );
 });
+
+/**
+ * Imports login data from a KeepassXC CSV file.
+ * `Title` is ignored until bug 1433770.
+ */
+add_task(async function test_import_from_keepassxc_csv() {
+  let csvFilePath = await setupCsv([
+    `"Group","Title","Username","Password","URL","Notes"`,
+    `"NewDatabase/Internet","Amazing","test@example.com","<password>","https://example.org",""`,
+  ]);
+
+  await LoginCSVImport.importFromCSV(csvFilePath);
+
+  LoginTestUtils.checkLogins(
+    [
+      TestData.formLogin({
+        formActionOrigin: "",
+        httpRealm: null,
+        origin: "https://example.org",
+        password: "<password>",
+        passwordField: "",
+        timesUsed: 1,
+        username: "test@example.com",
+        usernameField: "",
+      }),
+    ],
+    "Check that a new KeepassXC login was added with the correct fields",
+    (a, e) =>
+      a.equals(e) &&
+      checkMetaInfo(a, e, ["timesUsed"]) &&
+      checkLoginNewlyCreated(a)
+  );
+});