--- a/browser/components/migration/EdgeProfileMigrator.js
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -14,16 +14,20 @@ function EdgeProfileMigrator() {
EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
EdgeProfileMigrator.prototype.getResources = function() {
let resources = [
MSMigrationUtils.getBookmarksMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
];
+ let windowsVaultFormPasswordsMigrator =
+ MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
+ windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
+ resources.push(windowsVaultFormPasswordsMigrator);
return resources.filter(r => r.exists);
};
/* Somewhat counterintuitively, this returns:
* - |null| to indicate "There is only 1 (default) profile" (on win10+)
* - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid using this migrator.
* See MigrationUtils.jsm for slightly more info on how sourceProfiles is used.
*/
--- a/browser/components/migration/IEProfileMigrator.js
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -7,33 +7,38 @@
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
const kLoginsKey = "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
+Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
Cu.import("resource:///modules/MSMigrationUtils.jsm");
+Cu.import("resource://gre/modules/LoginHelper.jsm");
+
XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
"resource://gre/modules/ctypes.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
"resource://gre/modules/OSCrypto.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
"resource://gre/modules/WindowsRegistry.jsm");
Cu.importGlobalProperties(["URL"]);
+let CtypesKernelHelpers = MSMigrationUtils.CtypesKernelHelpers;
+
////////////////////////////////////////////////////////////////////////////////
//// Resources
function History() {
}
History.prototype = {
@@ -120,22 +125,29 @@ History.prototype = {
aCallback(this._success);
}
});
}
};
// IE password migrator supporting windows from XP until 7 and IE from 7 until 11
function IE7FormPasswords () {
+ // used to distinguish between this migrator and other passwords migrators in tests.
+ this.name = "IE7FormPasswords";
}
IE7FormPasswords.prototype = {
type: MigrationUtils.resourceTypes.PASSWORDS,
get exists() {
+ // work only on windows until 7
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
+ return false;
+ }
+
try {
let nsIWindowsRegKey = Ci.nsIWindowsRegKey;
let key = Cc["@mozilla.org/windows-registry-key;1"].
createInstance(nsIWindowsRegKey);
key.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, kLoginsKey,
nsIWindowsRegKey.ACCESS_READ);
let count = key.valueCount;
key.close();
@@ -165,17 +177,17 @@ IE7FormPasswords.prototype = {
aCallback(true);
},
/**
* Migrate the logins that were saved for the uris arguments.
* @param {nsIURI[]} uris - the uris that are going to be migrated.
*/
_migrateURIs(uris) {
- this.ctypesHelpers = new MSMigrationUtils.CtypesHelpers();
+ this.ctypesKernelHelpers = new MSMigrationUtils.CtypesKernelHelpers();
this._crypto = new OSCrypto();
let nsIWindowsRegKey = Ci.nsIWindowsRegKey;
let key = Cc["@mozilla.org/windows-registry-key;1"].
createInstance(nsIWindowsRegKey);
key.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, kLoginsKey,
nsIWindowsRegKey.ACCESS_READ);
let urlsSet = new Set(); // set of the already processed urls.
@@ -234,72 +246,36 @@ IE7FormPasswords.prototype = {
Cu.reportError("We failed to decrypt and import some logins. " +
"This is likely because we didn't find the URLs where these " +
"passwords were submitted in the IE history and which are needed to be used " +
"as keys in the decryption.");
}
key.close();
this._crypto.finalize();
- this.ctypesHelpers.finalize();
+ this.ctypesKernelHelpers.finalize();
},
_crypto: null,
/**
* Add the logins to the password manager.
* @param {Object[]} logins - array of the login details.
*/
_addLogins(ieLogins) {
- function addLogin(login, existingLogins) {
- // Add the login only if it doesn't already exist
- // if the login is not already available, it s going to be added or merged with another
- // login
- if (existingLogins.some(l => login.matches(l, true))) {
- return;
- }
- let isUpdate = false; // the login is just an update for an old one
- for (let existingLogin of existingLogins) {
- if (login.username == existingLogin.username && login.password != existingLogin.password) {
- // if a login with the same username and different password already exists and it's older
- // than the current one, that login needs to be updated using the current one details
- if (login.timePasswordChanged > existingLogin.timePasswordChanged) {
- // Bug 1187190: Password changes should be propagated depending on timestamps.
-
- // the existing login password and timestamps should be updated
- let propBag = Cc["@mozilla.org/hash-property-bag;1"].
- createInstance(Ci.nsIWritablePropertyBag);
- propBag.setProperty("password", login.password);
- propBag.setProperty("timePasswordChanged", login.timePasswordChanged);
- Services.logins.modifyLogin(existingLogin, propBag);
- // make sure not to add the new login
- isUpdate = true;
- }
- }
- }
- // if the new login is not an update, add it.
- if (!isUpdate) {
- Services.logins.addLogin(login);
- }
- }
-
for (let ieLogin of ieLogins) {
try {
- let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
-
- login.init(ieLogin.url, "", null,
- ieLogin.username, ieLogin.password, "", "");
- login.QueryInterface(Ci.nsILoginMetaInfo);
- login.timeCreated = ieLogin.creation;
- login.timeLastUsed = ieLogin.creation;
- login.timePasswordChanged = ieLogin.creation;
- // login.timesUsed is going to set to the default value 1
- // Add the login only if there's not an existing entry
- let existingLogins = Services.logins.findLogins({}, login.hostname, "", null);
- addLogin(login, existingLogins);
+ // create a new login
+ let login = {
+ username: ieLogin.username,
+ password: ieLogin.password,
+ hostname: ieLogin.url,
+ timeCreated: ieLogin.creation,
+ };
+ LoginHelper.maybeImportLogin(login);
} catch (e) {
Cu.reportError(e);
}
}
},
/**
* Extract the details of one or more logins from the raw decrypted data.
@@ -359,17 +335,17 @@ IE7FormPasswords.prototype = {
loginItem.ptr);
// currentLoginData.dataMax is the data count: each username and password is considered as
// a data. So, the number of logins is the number of data dived by 2
let numLogins = currentLoginData.dataMax / 2;
for (let n = 0; n < numLogins; n++) {
// Bytes 0-31 starting from currentInfoIndex contain the loginItem data structure for the
// current login
let currentLoginItem = currentLoginItemPointer.contents;
- let creation = this.ctypesHelpers.
+ let creation = this.ctypesKernelHelpers.
fileTimeToSecondsSinceEpoch(currentLoginItem.hiDateTime,
currentLoginItem.loDateTime) * 1000;
let currentResult = {
creation: creation,
url: url,
};
// The username is UTF-16 and null-terminated.
currentResult.username =
@@ -522,16 +498,20 @@ IEProfileMigrator.prototype = Object.cre
IEProfileMigrator.prototype.getResources = function IE_getResources() {
let resources = [
MSMigrationUtils.getBookmarksMigrator()
, new History()
, MSMigrationUtils.getCookiesMigrator()
, new IE7FormPasswords()
, new Settings()
];
+ let windowsVaultFormPasswordsMigrator =
+ MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
+ windowsVaultFormPasswordsMigrator.name = "IEVaultFormPasswords";
+ resources.push(windowsVaultFormPasswordsMigrator);
return [r for each (r in resources) if (r.exists)];
};
Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
get: function IE_get_sourceHomePageURL() {
let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
kMainKey, "Default_Page_URL");
let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
--- a/browser/components/migration/MSMigrationUtils.jsm
+++ b/browser/components/migration/MSMigrationUtils.jsm
@@ -3,92 +3,116 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["MSMigrationUtils"];
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
"resource://gre/modules/WindowsRegistry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
"resource://gre/modules/ctypes.jsm");
const EDGE_COOKIE_PATH_OPTIONS = ["", "#!001\\", "#!002\\"];
const EDGE_COOKIES_SUFFIX = "MicrosoftEdge\\Cookies";
const EDGE_FAVORITES = "AC\\MicrosoftEdge\\User\\Default\\Favorites";
const EDGE_READINGLIST = "AC\\MicrosoftEdge\\User\\Default\\DataStore\\Data\\";
+const FREE_CLOSE_FAILED = 0;
+const INTERNET_EXPLORER_EDGE_GUID = [0x3CCD5499,
+ 0x4B1087A8,
+ 0x886015A2,
+ 0x553BDD88];
+const RESULT_SUCCESS = 0;
+const VAULT_ENUMERATE_ALL_ITEMS = 512;
+const WEB_CREDENTIALS_VAULT_ID = [0x4BF4C442,
+ 0x41A09B8A,
+ 0x4ADD80B3,
+ 0x28DB4D70];
Cu.importGlobalProperties(["File"]);
////////////////////////////////////////////////////////////////////////////////
//// Helpers.
-function CtypesHelpers() {
+const wintypes = {
+ BOOL: ctypes.int,
+ DWORD: ctypes.uint32_t,
+ DWORDLONG: ctypes.uint64_t,
+ CHAR: ctypes.char,
+ PCHAR: ctypes.char.ptr,
+ LPCWSTR: ctypes.char16_t.ptr,
+ PDWORD: ctypes.uint32_t.ptr,
+ VOIDP: ctypes.voidptr_t,
+ WORD: ctypes.uint16_t,
+}
+
+// TODO: Bug 1202978 - Refactor MSMigrationUtils ctypes helpers
+function CtypesKernelHelpers() {
this._structs = {};
this._functions = {};
this._libs = {};
- const WORD = ctypes.uint16_t;
- const DWORD = ctypes.uint32_t;
- const BOOL = ctypes.int;
-
- this._structs.SYSTEMTIME = new ctypes.StructType('SYSTEMTIME', [
- {wYear: WORD},
- {wMonth: WORD},
- {wDayOfWeek: WORD},
- {wDay: WORD},
- {wHour: WORD},
- {wMinute: WORD},
- {wSecond: WORD},
- {wMilliseconds: WORD}
+ this._structs.SYSTEMTIME = new ctypes.StructType("SYSTEMTIME", [
+ {wYear: wintypes.WORD},
+ {wMonth: wintypes.WORD},
+ {wDayOfWeek: wintypes.WORD},
+ {wDay: wintypes.WORD},
+ {wHour: wintypes.WORD},
+ {wMinute: wintypes.WORD},
+ {wSecond: wintypes.WORD},
+ {wMilliseconds: wintypes.WORD}
]);
- this._structs.FILETIME = new ctypes.StructType('FILETIME', [
- {dwLowDateTime: DWORD},
- {dwHighDateTime: DWORD}
+ this._structs.FILETIME = new ctypes.StructType("FILETIME", [
+ {dwLowDateTime: wintypes.DWORD},
+ {dwHighDateTime: wintypes.DWORD}
]);
try {
this._libs.kernel32 = ctypes.open("Kernel32");
+
this._functions.FileTimeToSystemTime =
this._libs.kernel32.declare("FileTimeToSystemTime",
ctypes.default_abi,
- BOOL,
+ wintypes.BOOL,
this._structs.FILETIME.ptr,
this._structs.SYSTEMTIME.ptr);
} catch (ex) {
this.finalize();
}
}
-CtypesHelpers.prototype = {
+CtypesKernelHelpers.prototype = {
/**
* Must be invoked once after last use of any of the provided helpers.
*/
finalize() {
this._structs = {};
this._functions = {};
for each (let lib in this._libs) {
try {
lib.close();
} catch (ex) {}
}
this._libs = {};
},
- /**
+ /**
* Converts a FILETIME struct (2 DWORDS), to a SYSTEMTIME struct,
* and then deduces the number of seconds since the epoch (which
* is the data we want for the cookie expiry date).
*
* @param aTimeHi
* Least significant DWORD.
* @param aTimeLo
* Most significant DWORD.
@@ -111,16 +135,145 @@ CtypesHelpers.prototype = {
systemTime.wDay,
systemTime.wHour,
systemTime.wMinute,
systemTime.wSecond,
systemTime.wMilliseconds) / 1000);
}
};
+function CtypesVaultHelpers() {
+ this._structs = {};
+ this._functions = {};
+ // the size of the vault handle in 32 bits version is 32 and 64 in 64 bits version
+ if (wintypes.VOIDP.size == 4) {
+ this._vaultHandleType = wintypes.DWORD;
+ } else {
+ this._vaultHandleType = wintypes.DWORDLONG;
+ }
+
+ this._structs.GUID = new ctypes.StructType("GUID", [
+ {id: wintypes.DWORD.array(4)},
+ ]);
+
+ this._structs.VAULT_ITEM_ELEMENT = new ctypes.StructType("VAULT_ITEM_ELEMENT", [
+ // not documented
+ {schemaElementId: wintypes.DWORD},
+ // not documented
+ {unknown1: wintypes.DWORD},
+ // vault type
+ {type: wintypes.DWORD},
+ // not documented
+ {unknown2: wintypes.DWORD},
+ // value of the item
+ {itemValue: wintypes.LPCWSTR},
+ // not documented
+ {unknown3: wintypes.CHAR.array(12)},
+ ]);
+
+ this._structs.VAULT_ELEMENT = new ctypes.StructType("VAULT_ELEMENT", [
+ // vault item schemaId
+ {schemaId: this._structs.GUID},
+ // a pointer to the name of the browser VAULT_ITEM_ELEMENT
+ {pszCredentialFriendlyName: wintypes.LPCWSTR},
+ // a pointer to the url VAULT_ITEM_ELEMENT
+ {pResourceElement: this._structs.VAULT_ITEM_ELEMENT.ptr},
+ // a pointer to the username VAULT_ITEM_ELEMENT
+ {pIdentityElement: this._structs.VAULT_ITEM_ELEMENT.ptr},
+ // not documented
+ {pAuthenticatorElement: this._structs.VAULT_ITEM_ELEMENT.ptr},
+ // not documented
+ {pPackageSid: this._structs.VAULT_ITEM_ELEMENT.ptr},
+ // time stamp in local format
+ {lowLastModified: wintypes.DWORD},
+ {highLastModified: wintypes.DWORD},
+ // not documented
+ {flags: wintypes.DWORD},
+ // not documented
+ {dwPropertiesCount: wintypes.DWORD},
+ // not documented
+ {pPropertyElements: this._structs.VAULT_ITEM_ELEMENT.ptr},
+ ]);
+
+ try {
+ this._vaultcliLib = ctypes.open("vaultcli.dll");
+
+ this._functions.VaultOpenVault =
+ this._vaultcliLib.declare("VaultOpenVault",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ // GUID
+ this._structs.GUID.ptr,
+ // Flags
+ wintypes.DWORD,
+ // Vault Handle
+ this._vaultHandleType.ptr);
+ this._functions.VaultEnumerateItems =
+ this._vaultcliLib.declare("VaultEnumerateItems",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ // Vault Handle
+ this._vaultHandleType,
+ // Flags
+ wintypes.DWORD,
+ // Items Count
+ wintypes.PDWORD,
+ // Items
+ ctypes.voidptr_t);
+ this._functions.VaultCloseVault =
+ this._vaultcliLib.declare("VaultCloseVault",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ // Vault Handle
+ this._vaultHandleType);
+ this._functions.VaultGetItem =
+ this._vaultcliLib.declare("VaultGetItem",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ // Vault Handle
+ this._vaultHandleType,
+ // Schema Id
+ this._structs.GUID.ptr,
+ // Resource
+ this._structs.VAULT_ITEM_ELEMENT.ptr,
+ // Identity
+ this._structs.VAULT_ITEM_ELEMENT.ptr,
+ // Package Sid
+ this._structs.VAULT_ITEM_ELEMENT.ptr,
+ // HWND Owner
+ wintypes.DWORD,
+ // Flags
+ wintypes.DWORD,
+ // Items
+ this._structs.VAULT_ELEMENT.ptr.ptr);
+ this._functions.VaultFree =
+ this._vaultcliLib.declare("VaultFree",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ // Memory
+ this._structs.VAULT_ELEMENT.ptr);
+ } catch (ex) {
+ this.finalize();
+ }
+}
+
+CtypesVaultHelpers.prototype = {
+ /**
+ * Must be invoked once after last use of any of the provided helpers.
+ */
+ finalize() {
+ this._structs = {};
+ this._functions = {};
+ try {
+ this._vaultcliLib.close();
+ } catch (ex) {}
+ this._vaultcliLib = null;
+ }
+}
+
/**
* Checks whether an host is an IP (v4 or v6) address.
*
* @param aHost
* The host to check.
* @return whether aHost is an IP address.
*/
function hostIsIPAddress(aHost) {
@@ -157,17 +310,17 @@ function getEdgeLocalDataFolder() {
gEdgeDir = subDir;
return subDir.clone();
}
}
} catch (ex) {
Cu.reportError("Exception trying to find the Edge favorites directory: " + ex);
}
return null;
-}
+};
function Bookmarks(migrationType) {
this._migrationType = migrationType;
}
Bookmarks.prototype = {
type: MigrationUtils.resourceTypes.BOOKMARKS,
@@ -425,17 +578,17 @@ Cookies.prototype = {
folders.push(folder);
}
}
}
return this.__cookiesFolders = folders.length ? folders : null;
},
migrate(aCallback) {
- this.ctypesHelpers = new CtypesHelpers();
+ this.ctypesKernelHelpers = new CtypesKernelHelpers();
let cookiesGenerator = (function genCookie() {
let success = false;
let folders = this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE ?
this.__cookiesFolders : [this.__cookiesFolder];
for (let folder of folders) {
let entries = folder.directoryEntries;
while (entries.hasMoreElements()) {
@@ -452,17 +605,17 @@ Cookies.prototype = {
cookiesGenerator.next();
} catch (ex) {}
});
yield undefined;
}
}
- this.ctypesHelpers.finalize();
+ this.ctypesKernelHelpers.finalize();
aCallback(success);
}).apply(this);
cookiesGenerator.next();
},
_readCookieFile(aFile, aCallback) {
let fileReader = Cc["@mozilla.org/files/filereader;1"].
@@ -528,34 +681,173 @@ Cookies.prototype = {
if (host.length > 0) {
// Fist delete any possible extant matching host cookie.
Services.cookies.remove(host, name, path, false);
// Now make it a domain cookie.
if (host[0] != "." && !hostIsIPAddress(host))
host = "." + host;
}
- let expireTime = this.ctypesHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
+ let expireTime = this.ctypesKernelHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
Number(expireTimeLo));
Services.cookies.add(host,
path,
name,
value,
Number(flags) & 0x1, // secure
false, // httpOnly
false, // session
expireTime);
}
}
};
+// Migrator for form passwords on Windows 8 and higher.
+function WindowsVaultFormPasswords () {
+}
+
+WindowsVaultFormPasswords.prototype = {
+ type: MigrationUtils.resourceTypes.PASSWORDS,
+
+ get exists() {
+ // work only on windows 8+
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
+ // check if there are passwords available for migration.
+ return this.migrate(() => {}, true);
+ }
+ return false;
+ },
+
+ /**
+ * If aOnlyCheckExists is false, import the form passwords on Windows 8 and higher from the vault
+ * and then call the aCallback.
+ * Otherwise, check if there are passwords in the vault.
+ * @param {function} aCallback - a callback called when the migration is done.
+ * @param {boolean} [aOnlyCheckExists=false] - if aOnlyCheckExists is true, just check if there are some
+ * passwords to migrate. Import the passwords from the vault and call aCallback otherwise.
+ * @return true if there are passwords in the vault and aOnlyCheckExists is set to true,
+ * false if there is no password in the vault and aOnlyCheckExists is set to true, undefined if
+ * aOnlyCheckExists is set to false.
+ */
+ migrate(aCallback, aOnlyCheckExists = false) {
+ // check if the vault item is an IE/Edge one
+ function _isIEOrEdgePassword(id) {
+ return id[0] == INTERNET_EXPLORER_EDGE_GUID[0] &&
+ id[1] == INTERNET_EXPLORER_EDGE_GUID[1] &&
+ id[2] == INTERNET_EXPLORER_EDGE_GUID[2] &&
+ id[3] == INTERNET_EXPLORER_EDGE_GUID[3];
+ }
+
+ let ctypesVaultHelpers = new CtypesVaultHelpers();
+ let ctypesKernelHelpers = new CtypesKernelHelpers();
+ let migrationSucceeded = true;
+ let successfulVaultOpen = false;
+ let error, vault;
+ try {
+
+ // web credentials vault id
+ let vaultGuid = new ctypesVaultHelpers._structs.GUID(WEB_CREDENTIALS_VAULT_ID);
+ // number of available vaults
+ let vaultCount = new wintypes.DWORD;
+ error = new wintypes.DWORD;
+ // web credentials vault
+ vault = new ctypesVaultHelpers._vaultHandleType;
+ // open the current vault using the vaultGuid
+ error = ctypesVaultHelpers._functions.VaultOpenVault(vaultGuid.address(), 0, vault.address());
+ if (error != RESULT_SUCCESS) {
+ throw new Error("Unable to open Vault: " + error);
+ }
+ successfulVaultOpen = true;
+
+ let item = new ctypesVaultHelpers._structs.VAULT_ELEMENT.ptr;
+ let itemCount = new wintypes.DWORD;
+ // enumerate all the available items. This api is going to return a table of all the
+ // available items and item is going to point to the first element of this table.
+ error = ctypesVaultHelpers._functions.VaultEnumerateItems(vault, VAULT_ENUMERATE_ALL_ITEMS,
+ itemCount.address(),
+ item.address());
+ if (error != RESULT_SUCCESS) {
+ throw new Error("Unable to enumerate Vault items: " + error);
+ }
+ for (let j = 0; j < itemCount.value; j++) {
+ try {
+ // if it's not an ie/edge password, skip it
+ if (!_isIEOrEdgePassword(item.contents.schemaId.id)) {
+ continue;
+ }
+ // if aOnlyCheckExists is set to true, the purpose of the call is to return true if there is at
+ // least a password which is true in this case because a password was by now already found
+ if (aOnlyCheckExists) {
+ return true;
+ }
+ let url = item.contents.pResourceElement.contents.itemValue.readString();
+ let username = item.contents.pIdentityElement.contents.itemValue.readString();
+ // the current login credential object
+ let credential = new ctypesVaultHelpers._structs.VAULT_ELEMENT.ptr;
+ error = ctypesVaultHelpers._functions.VaultGetItem(vault,
+ item.contents.schemaId.address(),
+ item.contents.pResourceElement,
+ item.contents.pIdentityElement, null,
+ 0, 0, credential.address());
+ if (error != RESULT_SUCCESS) {
+ throw new Error("Unable to get item: " + error);
+ }
+
+ let password = credential.contents.pAuthenticatorElement.contents.itemValue.readString();
+ let creation = ctypesKernelHelpers.
+ fileTimeToSecondsSinceEpoch(item.contents.highLastModified,
+ item.contents.lowLastModified) * 1000;
+ // create a new login
+ let login = {
+ username, password,
+ hostname: NetUtil.newURI(url).prePath,
+ timeCreated: creation,
+ };
+ LoginHelper.maybeImportLogin(login);
+
+ // close current item
+ error = ctypesVaultHelpers._functions.VaultFree(credential);
+ if (error == FREE_CLOSE_FAILED) {
+ throw new Error("Unable to free item: " + error);
+ }
+ } catch (e) {
+ migrationSucceeded = false;
+ Cu.reportError(e);
+ } finally {
+ // move to next item in the table returned by VaultEnumerateItems
+ item = item.increment();
+ }
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ migrationSucceeded = false;
+ } finally {
+ if (successfulVaultOpen) {
+ // close current vault
+ error = ctypesVaultHelpers._functions.VaultCloseVault(vault);
+ if (error == FREE_CLOSE_FAILED) {
+ Cu.reportError("Unable to close vault: " + error);
+ }
+ }
+ ctypesKernelHelpers.finalize();
+ ctypesVaultHelpers.finalize();
+ aCallback(migrationSucceeded);
+ }
+ if (aOnlyCheckExists) {
+ return false;
+ }
+ }
+};
let MSMigrationUtils = {
MIGRATION_TYPE_IE: 1,
MIGRATION_TYPE_EDGE: 2,
- CtypesHelpers: CtypesHelpers,
+ CtypesKernelHelpers: CtypesKernelHelpers,
getBookmarksMigrator(migrationType = this.MIGRATION_TYPE_IE) {
return new Bookmarks(migrationType);
},
getCookiesMigrator(migrationType = this.MIGRATION_TYPE_IE) {
return new Cookies(migrationType);
},
+ getWindowsVaultFormPasswordsMigrator() {
+ return new WindowsVaultFormPasswords();
+ },
};
--- a/browser/components/migration/tests/unit/test_Edge_availability.js
+++ b/browser/components/migration/tests/unit/test_Edge_availability.js
@@ -1,11 +1,12 @@
const EDGE_AVAILABLE_MIGRATIONS =
MigrationUtils.resourceTypes.COOKIES |
- MigrationUtils.resourceTypes.BOOKMARKS;
+ MigrationUtils.resourceTypes.BOOKMARKS |
+ MigrationUtils.resourceTypes.PASSWORDS;
add_task(function* () {
let migrator = MigrationUtils.getMigrator("edge");
Cu.import("resource://gre/modules/AppConstants.jsm");
Assert.equal(!!(migrator && migrator.sourceExists), AppConstants.isPlatformAndVersionAtLeast("win", "10"),
"Edge should be available for migration if and only if we're on Win 10+");
if (migrator) {
let migratableData = migrator.getMigrateData(null, false);
--- a/browser/components/migration/tests/unit/test_IE7_passwords.js
+++ b/browser/components/migration/tests/unit/test_IE7_passwords.js
@@ -2,16 +2,17 @@ Cu.import("resource://gre/modules/AppCon
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
"resource://gre/modules/WindowsRegistry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
"resource://gre/modules/OSCrypto.jsm");
const CRYPT_PROTECT_UI_FORBIDDEN = 1;
+const IE7_FORM_PASSWORDS_MIGRATOR_NAME = "IE7FormPasswords";
const LOGINS_KEY = "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
const EXTENSION = "-backup";
const TESTED_WEBSITES = {
twitter: {
uri: makeURI("https://twitter.com"),
hash: "A89D42BC6406E27265B1AD0782B6F376375764A301",
data: [12, 0, 0, 0, 56, 0, 0, 0, 38, 0, 0, 0, 87, 73, 67, 75, 24, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 36, 67, 124, 118, 212, 208, 1, 8, 0, 0, 0, 18, 0, 0, 0, 68, 36, 67, 124, 118, 212, 208, 1, 9, 0, 0, 0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0, 0, 0, 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0, 57, 0, 0, 0],
logins: [
@@ -268,17 +269,17 @@ function createRegistryPath(path) {
}
function getFirstResourceOfType(type) {
let migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=ie"]
.createInstance(Ci.nsISupports)
.wrappedJSObject;
let migrators = migrator.getResources();
for (let m of migrators) {
- if (m.type == type) {
+ if (m.name == IE7_FORM_PASSWORDS_MIGRATOR_NAME && m.type == type) {
return m;
}
}
throw new Error("failed to find the " + type + " migrator");
}
function makeURI(aURL) {
return Services.io.newURI(aURL, null, null);
--- a/toolkit/components/passwordmgr/LoginHelper.jsm
+++ b/toolkit/components/passwordmgr/LoginHelper.jsm
@@ -337,9 +337,69 @@ this.LoginHelper = {
fieldType == "email" ||
fieldType == "url" ||
fieldType == "tel" ||
fieldType == "number") {
return true;
}
return false;
},
+
+ /**
+ * Add the login to the password manager if a similar one doesn't already exist. Merge it
+ * otherwise with the similar existing ones.
+ * @param {Object} loginData - the data about the login that needs to be added.
+ */
+ maybeImportLogin(loginData) {
+ // create a new login
+ let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
+ login.init(loginData.hostname,
+ loginData.submitURL || (typeof(loginData.httpRealm) == "string" ? null : ""),
+ typeof(loginData.httpRealm) == "string" ? loginData.httpRealm : null,
+ loginData.username,
+ loginData.password,
+ loginData.usernameElement || "",
+ loginData.passwordElement || "");
+
+ login.QueryInterface(Ci.nsILoginMetaInfo);
+ login.timeCreated = loginData.timeCreated;
+ login.timeLastUsed = loginData.timeLastUsed || loginData.timeCreated;
+ login.timePasswordChanged = loginData.timePasswordChanged || loginData.timeCreated;
+ login.timesUsed = loginData.timesUsed || 1;
+ // While here we're passing formSubmitURL and httpRealm, they could be empty/null and get
+ // ignored in that case, leading to multiple logins for the same username.
+ let existingLogins = Services.logins.findLogins({}, login.hostname,
+ login.formSubmitURL,
+ login.httpRealm);
+ // Add the login only if it doesn't already exist
+ // if the login is not already available, it's going to be added or merged with other
+ // logins
+ if (existingLogins.some(l => login.matches(l, true))) {
+ return;
+ }
+ // the login is just an update for an old one or the login is older than an existing one
+ let foundMatchingLogin = false;
+ for (let existingLogin of existingLogins) {
+ if (login.username == existingLogin.username) {
+ // Bug 1187190: Password changes should be propagated depending on timestamps.
+ // this an old login or a just an update, so make sure not to add it
+ foundMatchingLogin = true;
+ if(login.password != existingLogin.password &
+ login.timePasswordChanged > existingLogin.timePasswordChanged) {
+ // if a login with the same username and different password already exists and it's older
+ // than the current one, that login needs to be updated using the current one details
+
+ // the existing login password and timestamps should be updated
+ let propBag = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+ propBag.setProperty("password", login.password);
+ propBag.setProperty("timePasswordChanged", login.timePasswordChanged);
+ Services.logins.modifyLogin(existingLogin, propBag);
+ }
+ }
+ }
+ // if the new login is an update or is older than an exiting login, don't add it.
+ if (foundMatchingLogin) {
+ return;
+ }
+ Services.logins.addLogin(login);
+ }
};