services/sync/modules/engines/passwords.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial