services/sync/modules/engines/passwords.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 this.EXPORTED_SYMBOLS = ['PasswordEngine', 'LoginRec'];
michael@0 6
michael@0 7 const Cu = Components.utils;
michael@0 8 const Cc = Components.classes;
michael@0 9 const Ci = Components.interfaces;
michael@0 10 const Cr = Components.results;
michael@0 11
michael@0 12 Cu.import("resource://services-sync/record.js");
michael@0 13 Cu.import("resource://services-sync/constants.js");
michael@0 14 Cu.import("resource://services-sync/engines.js");
michael@0 15 Cu.import("resource://services-sync/util.js");
michael@0 16
michael@0 17 this.LoginRec = function LoginRec(collection, id) {
michael@0 18 CryptoWrapper.call(this, collection, id);
michael@0 19 }
michael@0 20 LoginRec.prototype = {
michael@0 21 __proto__: CryptoWrapper.prototype,
michael@0 22 _logName: "Sync.Record.Login",
michael@0 23 };
michael@0 24
michael@0 25 Utils.deferGetSet(LoginRec, "cleartext", ["hostname", "formSubmitURL",
michael@0 26 "httpRealm", "username", "password", "usernameField", "passwordField"]);
michael@0 27
michael@0 28
michael@0 29 this.PasswordEngine = function PasswordEngine(service) {
michael@0 30 SyncEngine.call(this, "Passwords", service);
michael@0 31 }
michael@0 32 PasswordEngine.prototype = {
michael@0 33 __proto__: SyncEngine.prototype,
michael@0 34 _storeObj: PasswordStore,
michael@0 35 _trackerObj: PasswordTracker,
michael@0 36 _recordObj: LoginRec,
michael@0 37 applyIncomingBatchSize: PASSWORDS_STORE_BATCH_SIZE,
michael@0 38
michael@0 39 get isAllowed() {
michael@0 40 return Cc["@mozilla.org/weave/service;1"]
michael@0 41 .getService(Ci.nsISupports)
michael@0 42 .wrappedJSObject
michael@0 43 .allowPasswordsEngine;
michael@0 44 },
michael@0 45
michael@0 46 get enabled() {
michael@0 47 // If we are disabled due to !isAllowed(), we must take care to ensure the
michael@0 48 // engine has actually had the enabled setter called which reflects this state.
michael@0 49 let prefVal = SyncEngine.prototype.__lookupGetter__("enabled").call(this);
michael@0 50 let newVal = this.isAllowed && prefVal;
michael@0 51 if (newVal != prefVal) {
michael@0 52 this.enabled = newVal;
michael@0 53 }
michael@0 54 return newVal;
michael@0 55 },
michael@0 56
michael@0 57 set enabled(val) {
michael@0 58 SyncEngine.prototype.__lookupSetter__("enabled").call(this, this.isAllowed && val);
michael@0 59 },
michael@0 60
michael@0 61 _syncFinish: function _syncFinish() {
michael@0 62 SyncEngine.prototype._syncFinish.call(this);
michael@0 63
michael@0 64 // Delete the weave credentials from the server once
michael@0 65 if (!Svc.Prefs.get("deletePwdFxA", false)) {
michael@0 66 try {
michael@0 67 let ids = [];
michael@0 68 for (let host of Utils.getSyncCredentialsHosts()) {
michael@0 69 for (let info of Services.logins.findLogins({}, host, "", "")) {
michael@0 70 ids.push(info.QueryInterface(Components.interfaces.nsILoginMetaInfo).guid);
michael@0 71 }
michael@0 72 }
michael@0 73 if (ids.length) {
michael@0 74 let coll = new Collection(this.engineURL, null, this.service);
michael@0 75 coll.ids = ids;
michael@0 76 let ret = coll.delete();
michael@0 77 this._log.debug("Delete result: " + ret);
michael@0 78 if (!ret.success && ret.status != 400) {
michael@0 79 // A non-400 failure means try again next time.
michael@0 80 return;
michael@0 81 }
michael@0 82 } else {
michael@0 83 this._log.debug("Didn't find any passwords to delete");
michael@0 84 }
michael@0 85 // If there were no ids to delete, or we succeeded, or got a 400,
michael@0 86 // record success.
michael@0 87 Svc.Prefs.set("deletePwdFxA", true);
michael@0 88 Svc.Prefs.reset("deletePwd"); // The old prefname we previously used.
michael@0 89 }
michael@0 90 catch(ex) {
michael@0 91 this._log.debug("Password deletes failed: " + Utils.exceptionStr(ex));
michael@0 92 }
michael@0 93 }
michael@0 94 },
michael@0 95
michael@0 96 _findDupe: function _findDupe(item) {
michael@0 97 let login = this._store._nsLoginInfoFromRecord(item);
michael@0 98 if (!login)
michael@0 99 return;
michael@0 100
michael@0 101 let logins = Services.logins.findLogins(
michael@0 102 {}, login.hostname, login.formSubmitURL, login.httpRealm);
michael@0 103 this._store._sleep(0); // Yield back to main thread after synchronous operation.
michael@0 104
michael@0 105 // Look for existing logins that match the hostname but ignore the password
michael@0 106 for each (let local in logins)
michael@0 107 if (login.matches(local, true) && local instanceof Ci.nsILoginMetaInfo)
michael@0 108 return local.guid;
michael@0 109 }
michael@0 110 };
michael@0 111
michael@0 112 function PasswordStore(name, engine) {
michael@0 113 Store.call(this, name, engine);
michael@0 114 this._nsLoginInfo = new Components.Constructor(
michael@0 115 "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
michael@0 116
michael@0 117 XPCOMUtils.defineLazyGetter(this, "DBConnection", function() {
michael@0 118 return Services.logins.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 119 .getInterface(Ci.mozIStorageConnection);
michael@0 120 });
michael@0 121 }
michael@0 122 PasswordStore.prototype = {
michael@0 123 __proto__: Store.prototype,
michael@0 124
michael@0 125 _nsLoginInfoFromRecord: function PasswordStore__nsLoginInfoRec(record) {
michael@0 126 if (record.formSubmitURL &&
michael@0 127 record.httpRealm) {
michael@0 128 this._log.warn("Record " + record.id +
michael@0 129 " has both formSubmitURL and httpRealm. Skipping.");
michael@0 130 return null;
michael@0 131 }
michael@0 132
michael@0 133 // Passing in "undefined" results in an empty string, which later
michael@0 134 // counts as a value. Explicitly `|| null` these fields according to JS
michael@0 135 // truthiness. Records with empty strings or null will be unmolested.
michael@0 136 function nullUndefined(x) (x == undefined) ? null : x;
michael@0 137 let info = new this._nsLoginInfo(record.hostname,
michael@0 138 nullUndefined(record.formSubmitURL),
michael@0 139 nullUndefined(record.httpRealm),
michael@0 140 record.username,
michael@0 141 record.password,
michael@0 142 record.usernameField,
michael@0 143 record.passwordField);
michael@0 144 info.QueryInterface(Ci.nsILoginMetaInfo);
michael@0 145 info.guid = record.id;
michael@0 146 return info;
michael@0 147 },
michael@0 148
michael@0 149 _getLoginFromGUID: function PasswordStore__getLoginFromGUID(id) {
michael@0 150 let prop = Cc["@mozilla.org/hash-property-bag;1"].
michael@0 151 createInstance(Ci.nsIWritablePropertyBag2);
michael@0 152 prop.setPropertyAsAUTF8String("guid", id);
michael@0 153
michael@0 154 let logins = Services.logins.searchLogins({}, prop);
michael@0 155 this._sleep(0); // Yield back to main thread after synchronous operation.
michael@0 156 if (logins.length > 0) {
michael@0 157 this._log.trace(logins.length + " items matching " + id + " found.");
michael@0 158 return logins[0];
michael@0 159 } else {
michael@0 160 this._log.trace("No items matching " + id + " found. Ignoring");
michael@0 161 }
michael@0 162 return null;
michael@0 163 },
michael@0 164
michael@0 165 applyIncomingBatch: function applyIncomingBatch(records) {
michael@0 166 if (!this.DBConnection) {
michael@0 167 return Store.prototype.applyIncomingBatch.call(this, records);
michael@0 168 }
michael@0 169
michael@0 170 return Utils.runInTransaction(this.DBConnection, function() {
michael@0 171 return Store.prototype.applyIncomingBatch.call(this, records);
michael@0 172 }, this);
michael@0 173 },
michael@0 174
michael@0 175 applyIncoming: function applyIncoming(record) {
michael@0 176 Store.prototype.applyIncoming.call(this, record);
michael@0 177 this._sleep(0); // Yield back to main thread after synchronous operation.
michael@0 178 },
michael@0 179
michael@0 180 getAllIDs: function PasswordStore__getAllIDs() {
michael@0 181 let items = {};
michael@0 182 let logins = Services.logins.getAllLogins({});
michael@0 183
michael@0 184 for (let i = 0; i < logins.length; i++) {
michael@0 185 // Skip over Weave password/passphrase entries
michael@0 186 let metaInfo = logins[i].QueryInterface(Ci.nsILoginMetaInfo);
michael@0 187 if (Utils.getSyncCredentialsHosts().has(metaInfo.hostname)) {
michael@0 188 continue;
michael@0 189 }
michael@0 190
michael@0 191 items[metaInfo.guid] = metaInfo;
michael@0 192 }
michael@0 193
michael@0 194 return items;
michael@0 195 },
michael@0 196
michael@0 197 changeItemID: function PasswordStore__changeItemID(oldID, newID) {
michael@0 198 this._log.trace("Changing item ID: " + oldID + " to " + newID);
michael@0 199
michael@0 200 let oldLogin = this._getLoginFromGUID(oldID);
michael@0 201 if (!oldLogin) {
michael@0 202 this._log.trace("Can't change item ID: item doesn't exist");
michael@0 203 return;
michael@0 204 }
michael@0 205 if (this._getLoginFromGUID(newID)) {
michael@0 206 this._log.trace("Can't change item ID: new ID already in use");
michael@0 207 return;
michael@0 208 }
michael@0 209
michael@0 210 let prop = Cc["@mozilla.org/hash-property-bag;1"].
michael@0 211 createInstance(Ci.nsIWritablePropertyBag2);
michael@0 212 prop.setPropertyAsAUTF8String("guid", newID);
michael@0 213
michael@0 214 Services.logins.modifyLogin(oldLogin, prop);
michael@0 215 },
michael@0 216
michael@0 217 itemExists: function PasswordStore__itemExists(id) {
michael@0 218 if (this._getLoginFromGUID(id))
michael@0 219 return true;
michael@0 220 return false;
michael@0 221 },
michael@0 222
michael@0 223 createRecord: function createRecord(id, collection) {
michael@0 224 let record = new LoginRec(collection, id);
michael@0 225 let login = this._getLoginFromGUID(id);
michael@0 226
michael@0 227 if (login) {
michael@0 228 record.hostname = login.hostname;
michael@0 229 record.formSubmitURL = login.formSubmitURL;
michael@0 230 record.httpRealm = login.httpRealm;
michael@0 231 record.username = login.username;
michael@0 232 record.password = login.password;
michael@0 233 record.usernameField = login.usernameField;
michael@0 234 record.passwordField = login.passwordField;
michael@0 235 }
michael@0 236 else
michael@0 237 record.deleted = true;
michael@0 238 return record;
michael@0 239 },
michael@0 240
michael@0 241 create: function PasswordStore__create(record) {
michael@0 242 let login = this._nsLoginInfoFromRecord(record);
michael@0 243 if (!login)
michael@0 244 return;
michael@0 245 this._log.debug("Adding login for " + record.hostname);
michael@0 246 this._log.trace("httpRealm: " + JSON.stringify(login.httpRealm) + "; " +
michael@0 247 "formSubmitURL: " + JSON.stringify(login.formSubmitURL));
michael@0 248 try {
michael@0 249 Services.logins.addLogin(login);
michael@0 250 } catch(ex) {
michael@0 251 this._log.debug("Adding record " + record.id +
michael@0 252 " resulted in exception " + Utils.exceptionStr(ex));
michael@0 253 }
michael@0 254 },
michael@0 255
michael@0 256 remove: function PasswordStore__remove(record) {
michael@0 257 this._log.trace("Removing login " + record.id);
michael@0 258
michael@0 259 let loginItem = this._getLoginFromGUID(record.id);
michael@0 260 if (!loginItem) {
michael@0 261 this._log.trace("Asked to remove record that doesn't exist, ignoring");
michael@0 262 return;
michael@0 263 }
michael@0 264
michael@0 265 Services.logins.removeLogin(loginItem);
michael@0 266 },
michael@0 267
michael@0 268 update: function PasswordStore__update(record) {
michael@0 269 let loginItem = this._getLoginFromGUID(record.id);
michael@0 270 if (!loginItem) {
michael@0 271 this._log.debug("Skipping update for unknown item: " + record.hostname);
michael@0 272 return;
michael@0 273 }
michael@0 274
michael@0 275 this._log.debug("Updating " + record.hostname);
michael@0 276 let newinfo = this._nsLoginInfoFromRecord(record);
michael@0 277 if (!newinfo)
michael@0 278 return;
michael@0 279 try {
michael@0 280 Services.logins.modifyLogin(loginItem, newinfo);
michael@0 281 } catch(ex) {
michael@0 282 this._log.debug("Modifying record " + record.id +
michael@0 283 " resulted in exception " + Utils.exceptionStr(ex) +
michael@0 284 ". Not modifying.");
michael@0 285 }
michael@0 286 },
michael@0 287
michael@0 288 wipe: function PasswordStore_wipe() {
michael@0 289 Services.logins.removeAllLogins();
michael@0 290 }
michael@0 291 };
michael@0 292
michael@0 293 function PasswordTracker(name, engine) {
michael@0 294 Tracker.call(this, name, engine);
michael@0 295 Svc.Obs.add("weave:engine:start-tracking", this);
michael@0 296 Svc.Obs.add("weave:engine:stop-tracking", this);
michael@0 297 }
michael@0 298 PasswordTracker.prototype = {
michael@0 299 __proto__: Tracker.prototype,
michael@0 300
michael@0 301 startTracking: function() {
michael@0 302 Svc.Obs.add("passwordmgr-storage-changed", this);
michael@0 303 },
michael@0 304
michael@0 305 stopTracking: function() {
michael@0 306 Svc.Obs.remove("passwordmgr-storage-changed", this);
michael@0 307 },
michael@0 308
michael@0 309 observe: function(subject, topic, data) {
michael@0 310 Tracker.prototype.observe.call(this, subject, topic, data);
michael@0 311
michael@0 312 if (this.ignoreAll) {
michael@0 313 return;
michael@0 314 }
michael@0 315
michael@0 316 // A single add, remove or change or removing all items
michael@0 317 // will trigger a sync for MULTI_DEVICE.
michael@0 318 switch (data) {
michael@0 319 case "modifyLogin":
michael@0 320 subject = subject.QueryInterface(Ci.nsIArray).queryElementAt(1, Ci.nsILoginMetaInfo);
michael@0 321 // fallthrough
michael@0 322 case "addLogin":
michael@0 323 case "removeLogin":
michael@0 324 // Skip over Weave password/passphrase changes.
michael@0 325 subject.QueryInterface(Ci.nsILoginMetaInfo).QueryInterface(Ci.nsILoginInfo);
michael@0 326 if (Utils.getSyncCredentialsHosts().has(subject.hostname)) {
michael@0 327 break;
michael@0 328 }
michael@0 329
michael@0 330 this.score += SCORE_INCREMENT_XLARGE;
michael@0 331 this._log.trace(data + ": " + subject.guid);
michael@0 332 this.addChangedID(subject.guid);
michael@0 333 break;
michael@0 334 case "removeAllLogins":
michael@0 335 this._log.trace(data);
michael@0 336 this.score += SCORE_INCREMENT_XLARGE;
michael@0 337 break;
michael@0 338 }
michael@0 339 }
michael@0 340 };

mercurial