services/sync/modules/engines/passwords.js

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:f62a4331d0cc
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/. */
4
5 this.EXPORTED_SYMBOLS = ['PasswordEngine', 'LoginRec'];
6
7 const Cu = Components.utils;
8 const Cc = Components.classes;
9 const Ci = Components.interfaces;
10 const Cr = Components.results;
11
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");
16
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 };
24
25 Utils.deferGetSet(LoginRec, "cleartext", ["hostname", "formSubmitURL",
26 "httpRealm", "username", "password", "usernameField", "passwordField"]);
27
28
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,
38
39 get isAllowed() {
40 return Cc["@mozilla.org/weave/service;1"]
41 .getService(Ci.nsISupports)
42 .wrappedJSObject
43 .allowPasswordsEngine;
44 },
45
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 },
56
57 set enabled(val) {
58 SyncEngine.prototype.__lookupSetter__("enabled").call(this, this.isAllowed && val);
59 },
60
61 _syncFinish: function _syncFinish() {
62 SyncEngine.prototype._syncFinish.call(this);
63
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 },
95
96 _findDupe: function _findDupe(item) {
97 let login = this._store._nsLoginInfoFromRecord(item);
98 if (!login)
99 return;
100
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.
104
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 };
111
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");
116
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,
124
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 }
132
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 },
148
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);
153
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 },
164
165 applyIncomingBatch: function applyIncomingBatch(records) {
166 if (!this.DBConnection) {
167 return Store.prototype.applyIncomingBatch.call(this, records);
168 }
169
170 return Utils.runInTransaction(this.DBConnection, function() {
171 return Store.prototype.applyIncomingBatch.call(this, records);
172 }, this);
173 },
174
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 },
179
180 getAllIDs: function PasswordStore__getAllIDs() {
181 let items = {};
182 let logins = Services.logins.getAllLogins({});
183
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 }
190
191 items[metaInfo.guid] = metaInfo;
192 }
193
194 return items;
195 },
196
197 changeItemID: function PasswordStore__changeItemID(oldID, newID) {
198 this._log.trace("Changing item ID: " + oldID + " to " + newID);
199
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 }
209
210 let prop = Cc["@mozilla.org/hash-property-bag;1"].
211 createInstance(Ci.nsIWritablePropertyBag2);
212 prop.setPropertyAsAUTF8String("guid", newID);
213
214 Services.logins.modifyLogin(oldLogin, prop);
215 },
216
217 itemExists: function PasswordStore__itemExists(id) {
218 if (this._getLoginFromGUID(id))
219 return true;
220 return false;
221 },
222
223 createRecord: function createRecord(id, collection) {
224 let record = new LoginRec(collection, id);
225 let login = this._getLoginFromGUID(id);
226
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 },
240
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 },
255
256 remove: function PasswordStore__remove(record) {
257 this._log.trace("Removing login " + record.id);
258
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 }
264
265 Services.logins.removeLogin(loginItem);
266 },
267
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 }
274
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 },
287
288 wipe: function PasswordStore_wipe() {
289 Services.logins.removeAllLogins();
290 }
291 };
292
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,
300
301 startTracking: function() {
302 Svc.Obs.add("passwordmgr-storage-changed", this);
303 },
304
305 stopTracking: function() {
306 Svc.Obs.remove("passwordmgr-storage-changed", this);
307 },
308
309 observe: function(subject, topic, data) {
310 Tracker.prototype.observe.call(this, subject, topic, data);
311
312 if (this.ignoreAll) {
313 return;
314 }
315
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 }
329
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